@stencil/vitest 0.1.0 → 0.1.3
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 +60 -266
- package/dist/testing/happy-dom-setup.d.ts +12 -0
- package/dist/testing/happy-dom-setup.d.ts.map +1 -0
- package/dist/testing/happy-dom-setup.js +16 -0
- package/dist/testing/jsdom-setup.d.ts +30 -0
- package/dist/testing/jsdom-setup.d.ts.map +1 -0
- package/dist/testing/jsdom-setup.js +95 -0
- package/dist/testing/mock-doc-setup.d.ts +17 -0
- package/dist/testing/mock-doc-setup.d.ts.map +1 -0
- package/dist/testing/mock-doc-setup.js +90 -0
- package/dist/utils/config-loader.d.ts +19 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +92 -0
- package/package.json +17 -20
package/dist/bin/stencil-test.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
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';
|
|
6
3
|
function parseArgs(argv) {
|
|
7
4
|
const parsed = {
|
|
8
5
|
watch: false,
|
|
@@ -40,10 +37,6 @@ function parseArgs(argv) {
|
|
|
40
37
|
parsed.port = argv[i + 1];
|
|
41
38
|
i += 2;
|
|
42
39
|
break;
|
|
43
|
-
case '--stencil-config':
|
|
44
|
-
parsed.stencilConfig = argv[i + 1];
|
|
45
|
-
i += 2;
|
|
46
|
-
break;
|
|
47
40
|
case '--verbose':
|
|
48
41
|
case '-v':
|
|
49
42
|
parsed.verbose = true;
|
|
@@ -123,13 +116,12 @@ Default (no flags): Build Stencil in dev mode and run tests once
|
|
|
123
116
|
Watch mode: Build Stencil in watch mode and run Vitest with interactive features
|
|
124
117
|
|
|
125
118
|
Stencil Options:
|
|
126
|
-
--watch
|
|
127
|
-
--prod
|
|
128
|
-
--serve
|
|
129
|
-
--port <number>
|
|
130
|
-
--
|
|
131
|
-
--
|
|
132
|
-
--debug Enable Stencil debug mode
|
|
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
|
|
133
125
|
|
|
134
126
|
Vitest Options:
|
|
135
127
|
--project <name> Run tests for specific project
|
|
@@ -264,161 +256,6 @@ function handleStencilOutput(data) {
|
|
|
264
256
|
}
|
|
265
257
|
}
|
|
266
258
|
}
|
|
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;
|
|
422
259
|
/**
|
|
423
260
|
* Clean up child processes and optionally exit with code.
|
|
424
261
|
*/
|
|
@@ -435,18 +272,6 @@ function cleanup(exitCode) {
|
|
|
435
272
|
stencilProcess.kill();
|
|
436
273
|
stencilProcess = null;
|
|
437
274
|
}
|
|
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
|
-
}
|
|
450
275
|
if (typeof exitCode === 'number') {
|
|
451
276
|
process.exit(exitCode);
|
|
452
277
|
}
|
|
@@ -454,94 +279,63 @@ function cleanup(exitCode) {
|
|
|
454
279
|
// Set up signal handlers for clean shutdown
|
|
455
280
|
process.on('SIGINT', () => cleanup());
|
|
456
281
|
process.on('SIGTERM', () => cleanup());
|
|
457
|
-
//
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
stencilArgs.push('--prod');
|
|
474
|
-
}
|
|
475
|
-
else {
|
|
476
|
-
stencilArgs.push('--dev');
|
|
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
|
-
}
|
|
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);
|
|
497
298
|
}
|
|
498
299
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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}`);
|
|
505
325
|
}
|
|
506
|
-
|
|
507
|
-
|
|
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;
|
|
508
330
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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');
|
|
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);
|
|
543
335
|
}
|
|
544
|
-
})().catch((error) => {
|
|
545
|
-
console.error('[stencil-test] Fatal error:', error);
|
|
546
|
-
cleanup(1);
|
|
547
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
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup file for happy-dom environment
|
|
3
|
+
* Auto-loaded when using a project named 'happy-dom' or containing 'happy-dom'
|
|
4
|
+
*
|
|
5
|
+
* Configures happy-dom with Stencil-specific setup
|
|
6
|
+
* happy-dom generally has better built-in support than jsdom, so fewer polyfills are needed
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Main setup function for happy-dom environment
|
|
10
|
+
*/
|
|
11
|
+
export declare function setup(): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=happy-dom-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"happy-dom-setup.d.ts","sourceRoot":"","sources":["../../src/testing/happy-dom-setup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;GAEG;AACH,wBAAsB,KAAK,kBAG1B"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup file for happy-dom environment
|
|
3
|
+
* Auto-loaded when using a project named 'happy-dom' or containing 'happy-dom'
|
|
4
|
+
*
|
|
5
|
+
* Configures happy-dom with Stencil-specific setup
|
|
6
|
+
* happy-dom generally has better built-in support than jsdom, so fewer polyfills are needed
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Main setup function for happy-dom environment
|
|
10
|
+
*/
|
|
11
|
+
export async function setup() {
|
|
12
|
+
// happy-dom is generally more complete than jsdom out of the box
|
|
13
|
+
// Add polyfills here as needed when issues are discovered
|
|
14
|
+
}
|
|
15
|
+
// Auto-run setup
|
|
16
|
+
setup();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup jsdom environment for Stencil component testing
|
|
3
|
+
*
|
|
4
|
+
* This module provides polyfills and initialization for testing Stencil components
|
|
5
|
+
* in a jsdom environment. It handles:
|
|
6
|
+
* - Polyfilling adoptedStyleSheets for Shadow DOM
|
|
7
|
+
* - Polyfilling CSS support detection
|
|
8
|
+
* - Polyfilling requestAnimationFrame and related APIs
|
|
9
|
+
* - Loading and initializing Stencil lazy loader
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // vitest.config.ts
|
|
14
|
+
* export default defineVitestConfig({
|
|
15
|
+
* test: {
|
|
16
|
+
* setupFiles: ['@stencil/test-utils/jsdom-setup'],
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Apply polyfills to a jsdom window object for Stencil components
|
|
23
|
+
* This function is reused by both the setup file and the custom environment
|
|
24
|
+
*/
|
|
25
|
+
export declare function applyJsdomPolyfills(window: Window & typeof globalThis): void;
|
|
26
|
+
/**
|
|
27
|
+
* Initialize jsdom environment for Stencil testing
|
|
28
|
+
*/
|
|
29
|
+
export declare function setup(): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=jsdom-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsdom-setup.d.ts","sourceRoot":"","sources":["../../src/testing/jsdom-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,UAAU,QA2DrE;AAED;;GAEG;AACH,wBAAsB,KAAK,kBAa1B"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup jsdom environment for Stencil component testing
|
|
3
|
+
*
|
|
4
|
+
* This module provides polyfills and initialization for testing Stencil components
|
|
5
|
+
* in a jsdom environment. It handles:
|
|
6
|
+
* - Polyfilling adoptedStyleSheets for Shadow DOM
|
|
7
|
+
* - Polyfilling CSS support detection
|
|
8
|
+
* - Polyfilling requestAnimationFrame and related APIs
|
|
9
|
+
* - Loading and initializing Stencil lazy loader
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // vitest.config.ts
|
|
14
|
+
* export default defineVitestConfig({
|
|
15
|
+
* test: {
|
|
16
|
+
* setupFiles: ['@stencil/test-utils/jsdom-setup'],
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Apply polyfills to a jsdom window object for Stencil components
|
|
23
|
+
* This function is reused by both the setup file and the custom environment
|
|
24
|
+
*/
|
|
25
|
+
export function applyJsdomPolyfills(window) {
|
|
26
|
+
// Polyfill adoptedStyleSheets for Shadow DOM
|
|
27
|
+
if (!window.document.adoptedStyleSheets) {
|
|
28
|
+
Object.defineProperty(window.document, 'adoptedStyleSheets', {
|
|
29
|
+
value: [],
|
|
30
|
+
writable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (typeof window.ShadowRoot !== 'undefined' &&
|
|
35
|
+
!Object.prototype.hasOwnProperty.call(window.ShadowRoot.prototype, 'adoptedStyleSheets')) {
|
|
36
|
+
Object.defineProperty(window.ShadowRoot.prototype, 'adoptedStyleSheets', {
|
|
37
|
+
get() {
|
|
38
|
+
if (!this._adoptedStyleSheets) {
|
|
39
|
+
this._adoptedStyleSheets = [];
|
|
40
|
+
}
|
|
41
|
+
return this._adoptedStyleSheets;
|
|
42
|
+
},
|
|
43
|
+
set(value) {
|
|
44
|
+
this._adoptedStyleSheets = value;
|
|
45
|
+
},
|
|
46
|
+
configurable: true,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// Polyfill CSS support
|
|
50
|
+
if (!window.CSS) {
|
|
51
|
+
window.CSS = {
|
|
52
|
+
supports: () => true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Polyfill scrollTo
|
|
56
|
+
window.scrollTo = () => { };
|
|
57
|
+
// Add requestAnimationFrame and related APIs
|
|
58
|
+
if (!window.requestAnimationFrame) {
|
|
59
|
+
window.requestAnimationFrame = (cb) => {
|
|
60
|
+
return setTimeout(cb, 0);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (!window.cancelAnimationFrame) {
|
|
64
|
+
window.cancelAnimationFrame = (id) => {
|
|
65
|
+
clearTimeout(id);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (!window.requestIdleCallback) {
|
|
69
|
+
window.requestIdleCallback = (cb) => {
|
|
70
|
+
return setTimeout(cb, 0);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (!window.cancelIdleCallback) {
|
|
74
|
+
window.cancelIdleCallback = (id) => {
|
|
75
|
+
clearTimeout(id);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Initialize jsdom environment for Stencil testing
|
|
81
|
+
*/
|
|
82
|
+
export async function setup() {
|
|
83
|
+
// Only run in jsdom environment (Node.js with DOM)
|
|
84
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Skip if running in actual browser (Playwright, WebdriverIO, etc.)
|
|
88
|
+
// In actual browsers, process is undefined or doesn't have cwd
|
|
89
|
+
if (typeof process === 'undefined' || typeof process.cwd !== 'function') {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
applyJsdomPolyfills(window);
|
|
93
|
+
}
|
|
94
|
+
// Auto-run setup when imported
|
|
95
|
+
setup();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup for mock-doc environment
|
|
3
|
+
* This file is automatically loaded when using the mock-doc environment in Node.js
|
|
4
|
+
*
|
|
5
|
+
* IMPORTANT: This should only be imported/executed in Node.js runtime, not in browsers.
|
|
6
|
+
* The projects-based config ensures this is only loaded for node:mock-doc projects.
|
|
7
|
+
*/
|
|
8
|
+
import { setupGlobal, teardownGlobal } from '@stencil/core/mock-doc';
|
|
9
|
+
/**
|
|
10
|
+
* Apply polyfills to a window object for Stencil components
|
|
11
|
+
* This function is reused by both the setup file and the custom environment
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyMockDocPolyfills(win: any): void;
|
|
14
|
+
declare let win: any;
|
|
15
|
+
declare let doc: any;
|
|
16
|
+
export { win, doc, setupGlobal, teardownGlobal };
|
|
17
|
+
//# sourceMappingURL=mock-doc-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-doc-setup.d.ts","sourceRoot":"","sources":["../../src/testing/mock-doc-setup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAc,WAAW,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAEjF;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,QAmD7C;AAOD,QAAA,IAAI,GAAG,EAAE,GAAG,CAAC;AACb,QAAA,IAAI,GAAG,EAAE,GAAG,CAAC;AA6Bb,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup for mock-doc environment
|
|
3
|
+
* This file is automatically loaded when using the mock-doc environment in Node.js
|
|
4
|
+
*
|
|
5
|
+
* IMPORTANT: This should only be imported/executed in Node.js runtime, not in browsers.
|
|
6
|
+
* The projects-based config ensures this is only loaded for node:mock-doc projects.
|
|
7
|
+
*/
|
|
8
|
+
import { MockWindow, setupGlobal, teardownGlobal } from '@stencil/core/mock-doc';
|
|
9
|
+
/**
|
|
10
|
+
* Apply polyfills to a window object for Stencil components
|
|
11
|
+
* This function is reused by both the setup file and the custom environment
|
|
12
|
+
*/
|
|
13
|
+
export function applyMockDocPolyfills(win) {
|
|
14
|
+
// Set baseURI manually
|
|
15
|
+
Object.defineProperty(win.document, 'baseURI', {
|
|
16
|
+
value: 'http://localhost:3000/',
|
|
17
|
+
writable: false,
|
|
18
|
+
configurable: true,
|
|
19
|
+
});
|
|
20
|
+
// Setup global with mock-doc globals
|
|
21
|
+
setupGlobal(win);
|
|
22
|
+
// Add MessageEvent if it doesn't exist (needed by Node's undici)
|
|
23
|
+
if (!win.MessageEvent) {
|
|
24
|
+
win.MessageEvent = class MessageEvent extends win.Event {
|
|
25
|
+
constructor(type, eventInitDict) {
|
|
26
|
+
super(type, eventInitDict);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Add AbortController if it doesn't exist
|
|
31
|
+
if (!win.AbortController) {
|
|
32
|
+
win.AbortController = class AbortController {
|
|
33
|
+
constructor() {
|
|
34
|
+
this.signal = {
|
|
35
|
+
aborted: false,
|
|
36
|
+
addEventListener: () => { },
|
|
37
|
+
removeEventListener: () => { },
|
|
38
|
+
dispatchEvent: () => true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
abort() {
|
|
42
|
+
this.signal.aborted = true;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Add requestAnimationFrame and related APIs
|
|
47
|
+
win.requestAnimationFrame = (cb) => {
|
|
48
|
+
return setTimeout(cb, 0);
|
|
49
|
+
};
|
|
50
|
+
win.cancelAnimationFrame = (id) => {
|
|
51
|
+
clearTimeout(id);
|
|
52
|
+
};
|
|
53
|
+
win.requestIdleCallback = (cb) => {
|
|
54
|
+
return setTimeout(cb, 0);
|
|
55
|
+
};
|
|
56
|
+
win.cancelIdleCallback = (id) => {
|
|
57
|
+
clearTimeout(id);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Only setup mock-doc if we're actually in Node.js (not a real browser)
|
|
61
|
+
// Check for Node.js-specific globals that don't exist in browsers
|
|
62
|
+
const isNodeEnvironment = typeof process !== 'undefined' && process?.versions?.node !== undefined && typeof window === 'undefined';
|
|
63
|
+
let win;
|
|
64
|
+
let doc;
|
|
65
|
+
if (!isNodeEnvironment) {
|
|
66
|
+
// We're in a real browser, skip mock-doc setup and export real globals
|
|
67
|
+
win = typeof window !== 'undefined' ? window : undefined;
|
|
68
|
+
doc = typeof document !== 'undefined' ? document : undefined;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// We're in Node.js, setup mock-doc
|
|
72
|
+
// Create mock window with URL
|
|
73
|
+
win = new MockWindow('http://localhost:3000/');
|
|
74
|
+
doc = win.document;
|
|
75
|
+
// Apply polyfills
|
|
76
|
+
applyMockDocPolyfills(win);
|
|
77
|
+
// Assign to globalThis
|
|
78
|
+
globalThis.window = win;
|
|
79
|
+
globalThis.document = doc;
|
|
80
|
+
globalThis.HTMLElement = win.HTMLElement;
|
|
81
|
+
globalThis.CustomEvent = win.CustomEvent;
|
|
82
|
+
globalThis.Event = win.Event;
|
|
83
|
+
globalThis.Element = win.Element;
|
|
84
|
+
globalThis.Node = win.Node;
|
|
85
|
+
globalThis.DocumentFragment = win.DocumentFragment;
|
|
86
|
+
globalThis.requestAnimationFrame = win.requestAnimationFrame;
|
|
87
|
+
globalThis.cancelAnimationFrame = win.cancelAnimationFrame;
|
|
88
|
+
}
|
|
89
|
+
// Export the mock window for use in custom setup
|
|
90
|
+
export { win, doc, setupGlobal, teardownGlobal };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Config as StencilConfig } from '@stencil/core/internal';
|
|
2
|
+
/**
|
|
3
|
+
* Load Stencil configuration from a file path
|
|
4
|
+
* Uses jiti to handle TypeScript files in Node.js
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadStencilConfig(configPath: string): Promise<StencilConfig | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* Get the source directory from Stencil config
|
|
9
|
+
*/
|
|
10
|
+
export declare function getStencilSrcDir(config?: StencilConfig): string;
|
|
11
|
+
/**
|
|
12
|
+
* Get all output directories from Stencil config for exclusion
|
|
13
|
+
*/
|
|
14
|
+
export declare function getStencilOutputDirs(config?: StencilConfig): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Create resolve aliases from Stencil config
|
|
17
|
+
*/
|
|
18
|
+
export declare function getStencilResolveAliases(config?: StencilConfig): Record<string, string>;
|
|
19
|
+
//# sourceMappingURL=config-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/utils/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAItE;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAsB9F;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAE/D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,EAAE,CAyCrE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBvF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Load Stencil configuration from a file path
|
|
5
|
+
* Uses jiti to handle TypeScript files in Node.js
|
|
6
|
+
*/
|
|
7
|
+
export async function loadStencilConfig(configPath) {
|
|
8
|
+
const resolvedPath = resolve(process.cwd(), configPath);
|
|
9
|
+
if (!existsSync(resolvedPath)) {
|
|
10
|
+
console.warn(`Stencil config not found at ${resolvedPath}`);
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
// Use jiti for loading TypeScript configs in Node.js
|
|
15
|
+
const { createJiti } = await import('jiti');
|
|
16
|
+
const jiti = createJiti(process.cwd(), {
|
|
17
|
+
interopDefault: true,
|
|
18
|
+
moduleCache: false,
|
|
19
|
+
});
|
|
20
|
+
const configModule = (await jiti.import(resolvedPath));
|
|
21
|
+
return configModule.config || configModule.default || configModule;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error(`Failed to load Stencil config from ${resolvedPath}:`, error);
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the source directory from Stencil config
|
|
30
|
+
*/
|
|
31
|
+
export function getStencilSrcDir(config) {
|
|
32
|
+
return config?.srcDir || 'src';
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get all output directories from Stencil config for exclusion
|
|
36
|
+
*/
|
|
37
|
+
export function getStencilOutputDirs(config) {
|
|
38
|
+
if (!config?.outputTargets) {
|
|
39
|
+
return ['dist', 'www', 'build', '.stencil'];
|
|
40
|
+
}
|
|
41
|
+
const outputDirs = new Set();
|
|
42
|
+
config.outputTargets.forEach((target) => {
|
|
43
|
+
// Add common output directories based on target type
|
|
44
|
+
if (target.dir) {
|
|
45
|
+
outputDirs.add(target.dir);
|
|
46
|
+
}
|
|
47
|
+
if (target.buildDir) {
|
|
48
|
+
outputDirs.add(target.buildDir);
|
|
49
|
+
}
|
|
50
|
+
// Handle Stencil default directories for output types that don't specify dir
|
|
51
|
+
// Based on Stencil's default behavior:
|
|
52
|
+
if (target.type === 'dist' || target.type === 'dist-custom-elements' || target.type === 'dist-hydrate-script') {
|
|
53
|
+
// These all default to 'dist' directory
|
|
54
|
+
if (!target.dir && !target.buildDir) {
|
|
55
|
+
outputDirs.add('dist');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (target.type === 'www') {
|
|
59
|
+
// www defaults to 'www' directory
|
|
60
|
+
if (!target.dir && !target.buildDir) {
|
|
61
|
+
outputDirs.add('www');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Note: esmLoaderPath is a relative import path like '../loader', not a directory
|
|
65
|
+
// We should NOT extract this as an exclude pattern
|
|
66
|
+
});
|
|
67
|
+
// Always include common output directories
|
|
68
|
+
outputDirs.add('.stencil');
|
|
69
|
+
// Filter out invalid paths (those that navigate up with ..)
|
|
70
|
+
const validDirs = Array.from(outputDirs).filter((dir) => !dir.includes('..'));
|
|
71
|
+
return validDirs.length > 0 ? validDirs : ['dist', 'www', 'build', '.stencil'];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create resolve aliases from Stencil config
|
|
75
|
+
*/
|
|
76
|
+
export function getStencilResolveAliases(config) {
|
|
77
|
+
const srcDir = getStencilSrcDir(config);
|
|
78
|
+
const aliases = {
|
|
79
|
+
'@': resolve(process.cwd(), srcDir),
|
|
80
|
+
};
|
|
81
|
+
// Add component alias if components directory exists
|
|
82
|
+
const componentsDir = resolve(process.cwd(), srcDir, 'components');
|
|
83
|
+
if (existsSync(componentsDir)) {
|
|
84
|
+
aliases['@components'] = componentsDir;
|
|
85
|
+
}
|
|
86
|
+
// Add utils alias if utils directory exists
|
|
87
|
+
const utilsDir = resolve(process.cwd(), srcDir, 'utils');
|
|
88
|
+
if (existsSync(utilsDir)) {
|
|
89
|
+
aliases['@utils'] = utilsDir;
|
|
90
|
+
}
|
|
91
|
+
return aliases;
|
|
92
|
+
}
|
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.1.
|
|
7
|
+
"version": "0.1.3",
|
|
8
8
|
"description": "First-class testing utilities for Stencil design systems with Vitest",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"type": "module",
|
|
@@ -56,22 +56,6 @@
|
|
|
56
56
|
"web components",
|
|
57
57
|
"component testing"
|
|
58
58
|
],
|
|
59
|
-
"scripts": {
|
|
60
|
-
"build": "tsc && tsc -p tsconfig.bin.json",
|
|
61
|
-
"dev": "tsc --watch",
|
|
62
|
-
"test:e2e": "pnpm --filter test-project test",
|
|
63
|
-
"test:unit": "vitest run",
|
|
64
|
-
"test:unit:watch": "vitest",
|
|
65
|
-
"test:engines": "pnpm installed-check --no-workspaces --ignore-dev",
|
|
66
|
-
"lint": "eslint .",
|
|
67
|
-
"lint:fix": "eslint . --fix",
|
|
68
|
-
"format": "prettier --check .",
|
|
69
|
-
"format:fix": "prettier --write .",
|
|
70
|
-
"knip": "knip",
|
|
71
|
-
"quality": "pnpm format && pnpm lint && pnpm knip",
|
|
72
|
-
"quality:fix": "pnpm format:fix && pnpm lint:fix && pnpm knip",
|
|
73
|
-
"release": "semantic-release"
|
|
74
|
-
},
|
|
75
59
|
"publishConfig": {
|
|
76
60
|
"access": "public"
|
|
77
61
|
},
|
|
@@ -108,7 +92,7 @@
|
|
|
108
92
|
"dependencies": {
|
|
109
93
|
"jiti": "^2.6.1",
|
|
110
94
|
"local-pkg": "^1.1.2",
|
|
111
|
-
"vitest-environment-stencil": "
|
|
95
|
+
"vitest-environment-stencil": "0.1.3"
|
|
112
96
|
},
|
|
113
97
|
"devDependencies": {
|
|
114
98
|
"@eslint/js": "^9.39.2",
|
|
@@ -132,5 +116,18 @@
|
|
|
132
116
|
"engines": {
|
|
133
117
|
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
|
|
134
118
|
},
|
|
135
|
-
"
|
|
136
|
-
|
|
119
|
+
"scripts": {
|
|
120
|
+
"build": "tsc && tsc -p tsconfig.bin.json",
|
|
121
|
+
"dev": "tsc --watch",
|
|
122
|
+
"test": "pnpm --filter test-project test",
|
|
123
|
+
"test:engines": "pnpm installed-check --no-workspaces --ignore-dev",
|
|
124
|
+
"lint": "eslint .",
|
|
125
|
+
"lint:fix": "eslint . --fix",
|
|
126
|
+
"format": "prettier --check .",
|
|
127
|
+
"format:fix": "prettier --write .",
|
|
128
|
+
"knip": "knip",
|
|
129
|
+
"quality": "pnpm format && pnpm lint && pnpm knip",
|
|
130
|
+
"quality:fix": "pnpm format:fix && pnpm lint:fix && pnpm knip",
|
|
131
|
+
"release": "semantic-release"
|
|
132
|
+
}
|
|
133
|
+
}
|