@stencil/vitest 1.11.3 → 1.11.4

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 CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  First-class testing utilities for Stencil components, powered by Vitest.
4
4
 
5
+ ## Table of Contents
6
+
7
+ - [Quick Start](#quick-start)
8
+ - [1. Install](#1-install)
9
+ - [2. Create vitest.config.ts](#2-create-vitestconfigts)
10
+ - [3. Load your components](#3-load-your-components)
11
+ - [4. Write Tests](#4-write-tests)
12
+ - [5. Run tests](#5-run-tests)
13
+ - [API](#api)
14
+ - [Rendering](#rendering)
15
+ - [Available matchers](#available-matchers)
16
+ - [Spying and Mocking](#spying-and-mocking)
17
+ - [Event Testing](#event-testing)
18
+ - [Stencil Vitest Plugin (Experimental)](#stencil-vitest-plugin)
19
+ - [Setup](#setup)
20
+ - [Mocking component dependencies](#mocking-component-dependencies)
21
+ - [Limitations](#limitations)
22
+ - [Snapshots](#snapshots)
23
+ - [Screenshot Testing](#screenshot-testing)
24
+ - [Utils](#utils)
25
+ - [serializeHtml](#serializehtmlelement-options)
26
+ - [prettifyHtml](#prettifyhtmlhtml)
27
+ - [waitForStable](#waitforstableelementorselector-timeout)
28
+ - [waitForExist](#waitforexistselector-timeout)
29
+ - [CLI](#cli)
30
+ - [Usage](#usage)
31
+ - [Flags](#flags)
32
+ - [Global Variables](#global-variables)
33
+ - [Limitations / Gotchas](#limitations--gotchas)
34
+ - [License](#license)
35
+ - [Contributing](#contributing)
36
+
5
37
  ## Quick Start
6
38
 
7
39
  ### 1. Install
@@ -442,9 +474,12 @@ expect(clickSpy.lastEvent?.detail).toEqual({ buttonId: 'my-button' });
442
474
 
443
475
  ## Stencil Vitest Plugin
444
476
 
445
- The recommended testing approach in this package is to test against **pre-built dist outputs** Stencil compiles your components once and tests run against those bundles. This is fast and reliable, but it does mean Vitest never sees individual component source files as discrete modules. As a result, `vi.mock()` cannot intercept imports made by your components, because the dependency is already bundled away before Vitest gets involved.
477
+ All examples so far have mentioned setting up tests against **pre-built dist outputs**; Stencil compiles your components once and tests run against those bundles. Whilst this method is fast and reliable, it does mean Vitest never sees individual component source files as discrete modules and so does have 2 key limitations:
478
+
479
+ 1. `vi.mock()` cannot intercept imports made by your components, because the dependency is already bundled away before Vitest gets involved.
480
+ 2. Coverage reports will not work out-of-the-box without additional configuration (`sourceMap: true` / [3rd party tools](https://github.com/cenfun/vitest-monocart-coverage)) and even then, may not be accurate.
446
481
 
447
- `stencilVitestPlugin` solves this by hooking into Vite's transform pipeline. Every `.tsx` file containing Stencil decorators is compiled on-the-fly via `transpileSync` before Vitest imports it, using `componentExport: 'customelement'`. This means each component file becomes its own entry in Vitest's module graph — and its imports are independently resolvable and mockable.
482
+ The experimental `stencilVitestPlugin` solves this by hooking into Vite's transform pipeline: Stencil files are compiled on-the-fly before Vitest imports them; each component file becomes its own entry in Vitest's module graph — and its imports are independently resolvable and mockable.
448
483
 
449
484
  ### Setup
450
485
 
@@ -461,11 +496,11 @@ export default defineVitestConfig({
461
496
  plugins: [stencilVitestPlugin()],
462
497
  test: {
463
498
  name: 'plugin',
464
- environment: 'stencil',
465
499
  include: ['src/**/*.plugin.spec.{ts,tsx}'],
466
500
  // No dist setup file needed — each component source file registers
467
- // itself via customElements.define() the moment it is imported.
468
- // Optional environment options
501
+
502
+ environment: 'stencil',
503
+ // ^^ you can use the plugin with any setup - even browser tests!
469
504
  },
470
505
  },
471
506
  ],
@@ -523,7 +558,7 @@ it('renders using the mocked utility', async () => {
523
558
 
524
559
  #### Class inheritance
525
560
 
526
- In Stencil v4 `transpileSync` (used within the plugin) is a single-file compiler. When a component class `extends` a base class that lives in a separate file, `transpileSync` cannot follow the import to merge the parent's metadata and will throw an error.
561
+ In Stencil v4 `transpile()` (used within the plugin) is a single-file compiler. When a component class `extends` a base class that lives in a separate file, `transpile()` cannot follow the import to merge the parent's metadata and will throw an error.
527
562
 
528
563
  ```tsx
529
564
  // ❌ Will fail — base class is in a separate file
@@ -533,7 +568,7 @@ import { FormBase } from './form-base.js';
533
568
  export class MyInput extends FormBase { ... }
534
569
  ```
535
570
 
536
- > This limitation is specific to v4. Stencil v5's compiler can resolve multi-file inheritance chains.
571
+ > This limitation is specific to v4. Stencil v5's `transpile()` can resolve multi-file inheritance chains.
537
572
 
538
573
  ## Snapshots
539
574
 
@@ -646,17 +681,6 @@ const element = await waitForExist('#dynamic-content', 10000);
646
681
 
647
682
  The `stencil-test` CLI wraps both Stencil builds with Vitest testing.
648
683
 
649
- ### Add to package.json
650
-
651
- ```json
652
- {
653
- "scripts": {
654
- "test": "stencil-test",
655
- "test:watch": "stencil-test --watch"
656
- }
657
- }
658
- ```
659
-
660
684
  ### Usage
661
685
 
662
686
  ```bash
@@ -682,12 +706,14 @@ stencil-test button.spec.ts
682
706
  stencil-test --project browser
683
707
  ```
684
708
 
685
- ### CLI Options
709
+ ### Flags
710
+
711
+ The `stencil-test` CLI supports most of Stencil's CLI flags and all of Vitest CLI flags
686
712
 
687
- The `stencil-test` CLI supports most of Stencil's CLI options and all of Vitest CLI options
713
+ - For full Stencil CLI flags, see [Stencil CLI docs](https://stenciljs.com/docs/cli).
714
+ - For full Vitest CLI flags, see [Vitest CLI docs](https://vitest.dev/guide/cli.html).
688
715
 
689
- - For full Stencil CLI options, see [Stencil CLI docs](https://stenciljs.com/docs/cli).
690
- - For full Vitest CLI options, see [Vitest CLI docs](https://vitest.dev/guide/cli.html).
716
+ Note: unlike a normal `stencil build` `stencil-vitest` runs in development mode by default for faster builds. Use `--prod` to test against a production build.
691
717
 
692
718
  ### Global Variables
693
719
 
@@ -721,6 +747,16 @@ Add to your `tsconfig.json` for type definitions:
721
747
  }
722
748
  ```
723
749
 
750
+ ## Limitations / Gotchas
751
+
752
+ ### `vi.mock()` doesn't work?
753
+
754
+ Modules can only be mocked if they are imported in a way that Vitest can intercept. If your components are importing dependencies that you want to mock, you must use the `stencilVitestPlugin` to compile components on-the-fly and allow Vitest to mock their imports. See the [Stencil Vitest Plugin section](#stencil-vitest-plugin) for details.
755
+
756
+ ### Coverage reports are empty or inaccurate?
757
+
758
+ When testing against pre-built dist outputs, source maps (`sourceMap: true` in `stencil.config.ts`) and [3rd party tools](https://github.com/cenfun/vitest-monocart-coverage) are required for coverage reports. Alternatively, consider using the `stencilVitestPlugin` which compiles components on-the-fly and provides better coverage support.
759
+
724
760
  ## License
725
761
 
726
762
  MIT
@@ -1 +1 @@
1
- {"version":3,"file":"stencil.d.ts","sourceRoot":"","sources":["../../src/environments/stencil.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAiBvD,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,cAAc,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;CACrD;AAQD;;;;;;;;;;;;;;;;GAgBG;wBACa,WAAW;AAA3B,wBAwEE"}
1
+ {"version":3,"file":"stencil.d.ts","sourceRoot":"","sources":["../../src/environments/stencil.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAiBvD,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,cAAc,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;CACrD;AAQD;;;;;;;;;;;;;;;;GAgBG;wBACa,WAAW;AAA3B,wBA6FE"}
@@ -77,6 +77,26 @@ export default {
77
77
  if (originals.has('Event')) {
78
78
  global.Event = originals.get('Event');
79
79
  }
80
+ // Create HTMLElement wrapper that captures ownerDocument
81
+ // In Stencil 4.43+, win.HTMLElement returns MockHTMLElement directly which expects
82
+ // ownerDocument as the first constructor arg. But Stencil-compiled components call
83
+ // super() with no args (standard browser behavior). This wrapper bridges the gap.
84
+ const MockHTMLElementBase = global.HTMLElement;
85
+ const doc = win.document;
86
+ global.HTMLElement = class extends MockHTMLElementBase {
87
+ constructor() {
88
+ super(doc, '');
89
+ const observedAttributes = this.constructor.observedAttributes;
90
+ if (Array.isArray(observedAttributes) && typeof this.attributeChangedCallback === 'function') {
91
+ observedAttributes.forEach((attrName) => {
92
+ const attrValue = this.getAttribute(attrName);
93
+ if (attrValue != null) {
94
+ this.attributeChangedCallback(attrName, null, attrValue);
95
+ }
96
+ });
97
+ }
98
+ }
99
+ };
80
100
  // Remove undefined properties that shadow native globals
81
101
  keys.forEach((key) => {
82
102
  if (global[key] === undefined && originals.has(key)) {
package/dist/plugin.d.ts CHANGED
@@ -33,7 +33,5 @@ import type { Plugin } from 'vitest/config';
33
33
  * @param opts Optional configuration for the plugin
34
34
  * @returns a Vite plugin configuration object
35
35
  */
36
- export declare function stencilVitestPlugin(opts?: {
37
- css?: boolean;
38
- }): Plugin;
36
+ export declare function stencilVitestPlugin(): Plugin;
39
37
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAkFxE"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAyF5C"}
package/dist/plugin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { transpile } from '@stencil/core/compiler';
2
- import { existsSync, readFileSync } from 'node:fs';
2
+ import { readFileSync } from 'node:fs';
3
3
  import { dirname, resolve } from 'node:path';
4
4
  /**
5
5
  * A Vite/Vitest plugin that transforms Stencil component source files (.tsx) on-the-fly,
@@ -35,11 +35,40 @@ import { dirname, resolve } from 'node:path';
35
35
  * @param opts Optional configuration for the plugin
36
36
  * @returns a Vite plugin configuration object
37
37
  */
38
- export function stencilVitestPlugin(opts = {}) {
38
+ export function stencilVitestPlugin() {
39
39
  return {
40
40
  name: 'stencil-vitest-transform',
41
41
  enforce: 'pre',
42
+ resolveId(id, importer) {
43
+ if (id.includes('.css') && id.includes('tag=')) {
44
+ const [relPath] = id.split('?');
45
+ const query = id.slice(id.indexOf('?'));
46
+ const resolved = resolve(dirname(importer), relPath);
47
+ // Remove .css from virtual ID to prevent Vite's CSS plugin from hijacking the output
48
+ return '\0stencil-style:' + resolved.replace(/\.css$/, '') + query;
49
+ }
50
+ return null;
51
+ },
52
+ load(id) {
53
+ if (id.startsWith('\0stencil-style:')) {
54
+ // Add .css back to get the real file path
55
+ const realPath = id.slice('\0stencil-style:'.length).split('?')[0] + '.css';
56
+ return readFileSync(realPath, 'utf-8');
57
+ }
58
+ return null;
59
+ },
42
60
  async transform(code, id) {
61
+ if (id.startsWith('\0stencil-style:')) {
62
+ // Reconstruct the original .css path for Stencil's transpiler (it uses extension to detect file type)
63
+ const pathWithoutPrefix = id.slice('\0stencil-style:'.length);
64
+ const [basePath, query] = pathWithoutPrefix.split('?');
65
+ const originalPath = basePath + '.css' + (query ? '?' + query : '');
66
+ const result = await transpile(code, { file: originalPath });
67
+ return {
68
+ code: result.code,
69
+ map: null,
70
+ };
71
+ }
43
72
  // Only transform .tsx files
44
73
  if (!id.endsWith('.tsx')) {
45
74
  return null;
@@ -66,10 +95,9 @@ export function stencilVitestPlugin(opts = {}) {
66
95
  module: 'esm',
67
96
  proxy: null,
68
97
  sourceMap: false,
69
- style: opts.css ? 'inline' : null,
70
- styleImportData: opts.css ? 'queryparams' : null,
98
+ style: 'static',
99
+ styleImportData: 'queryparams',
71
100
  target: 'es2022',
72
- // Don't rewrite import paths — let Vite handle resolution via aliases
73
101
  transformAliasedImportPaths: false,
74
102
  });
75
103
  const errors = result.diagnostics?.filter((d) => d.level === 'error') ?? [];
@@ -77,28 +105,8 @@ export function stencilVitestPlugin(opts = {}) {
77
105
  const messages = errors.map((d) => d.messageText).join('\n');
78
106
  throw new Error(`[stencil-vitest-plugin] Transform error in ${id}:\n${messages}`);
79
107
  }
80
- let transformedCode = result.code;
81
- // If CSS is enabled, inline the CSS imports as functions, - Stencil v4 always expects css to be a function, Vite
82
- // tries to handle the import as a string and errors.
83
- // In v5, Stencil supports both string and function styles, but the plugin opts for function style for consistency across versions.
84
- if (opts.css) {
85
- // Match: import SomeVar from "./path.css?tag=...&encapsulation=...";
86
- const cssImportRegex = /import\s+(\w+)\s+from\s+['"]([^'"]+\.css\?tag[^'"]+)['"]\s*;?/g;
87
- let match;
88
- while ((match = cssImportRegex.exec(result.code)) !== null) {
89
- const [fullMatch, varName, importPath] = match;
90
- const [relPath] = importPath.split('?');
91
- const absolutePath = resolve(dirname(id), relPath);
92
- if (existsSync(absolutePath)) {
93
- const css = readFileSync(absolutePath, 'utf-8');
94
- // Replace the import with an inline function
95
- const inlineFunc = `const ${varName} = () => ${JSON.stringify(css)};`;
96
- transformedCode = transformedCode.replace(fullMatch, inlineFunc);
97
- }
98
- }
99
- }
100
108
  return {
101
- code: transformedCode,
109
+ code: result.code,
102
110
  };
103
111
  }
104
112
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"mock-doc-setup.d.ts","sourceRoot":"","sources":["../../src/setup/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;AA4Bb,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"mock-doc-setup.d.ts","sourceRoot":"","sources":["../../src/setup/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"}
@@ -80,6 +80,7 @@ else {
80
80
  globalThis.document = doc;
81
81
  globalThis.HTMLElement = win.HTMLElement;
82
82
  globalThis.CustomEvent = win.CustomEvent;
83
+ globalThis.Event = win.Event;
83
84
  globalThis.Element = win.Element;
84
85
  globalThis.Node = win.Node;
85
86
  globalThis.DocumentFragment = win.DocumentFragment;
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.11.3",
7
+ "version": "1.11.4",
8
8
  "description": "First-class testing utilities for Stencil design systems with Vitest",
9
9
  "license": "MIT",
10
10
  "type": "module",
@@ -104,7 +104,7 @@
104
104
  "dependencies": {
105
105
  "jiti": "^2.6.1",
106
106
  "local-pkg": "^1.1.2",
107
- "vitest-environment-stencil": "1.11.3"
107
+ "vitest-environment-stencil": "1.11.4"
108
108
  },
109
109
  "devDependencies": {
110
110
  "@eslint/js": "^9.39.2",