@stencil/vitest 1.8.3 → 1.9.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/README.md CHANGED
@@ -155,6 +155,18 @@ await setProps({ name: 'Stencil' });
155
155
  unmount();
156
156
  ```
157
157
 
158
+ #### `waitForReady` Option
159
+
160
+ By default, `render()` waits for components to be fully hydrated before returning. It detects when Stencil applies the hydrated flag (class or attribute) to your component, respecting your `stencil.config` settings.
161
+
162
+ ```tsx
163
+ // Default behaviour - waits for hydration
164
+ const { root } = await render(<my-component />);
165
+
166
+ // Skip hydration wait (useful for testing loading states)
167
+ const { root } = await render(<my-component />, { waitForReady: false });
168
+ ```
169
+
158
170
  ### Available matchers:
159
171
 
160
172
  ```typescript
@@ -177,6 +189,231 @@ await expect(element).toEqualHtml('<div>Expected HTML</div>');
177
189
  await expect(element).toEqualLightHtml('<div>Light DOM only</div>');
178
190
  ```
179
191
 
192
+ ### Spying and Mocking
193
+
194
+ Spy on component methods, props, and lifecycle hooks to verify behaviour without modifying your component code.
195
+
196
+ > **Setup requirement:** Load your components in a `beforeAll` block (typically in your setup file). The spy system patches `customElements.define`, so components must be registered after the test framework initializes.
197
+ >
198
+ > ```diff
199
+ > // vitest-setup.ts
200
+ > - await import('./dist/test-components/test-components.esm.js');
201
+ >
202
+ > + import { beforeAll } from 'vitest';
203
+ > + beforeAll(async () => {
204
+ > + await import('./dist/test-components/test-components.esm.js');
205
+ > + });
206
+ > ```
207
+
208
+ #### Method Spying
209
+
210
+ Spy on methods while still calling the original implementation:
211
+
212
+ ```tsx
213
+ const { root, spies } = await render(<my-button>Click me</my-button>, {
214
+ spyOn: {
215
+ methods: ['handleClick'],
216
+ },
217
+ });
218
+
219
+ // Trigger the method
220
+ root.shadowRoot?.querySelector('button')?.click();
221
+
222
+ // Assert the method was called
223
+ expect(spies?.methods.handleClick).toHaveBeenCalledTimes(1);
224
+ expect(spies?.methods.handleClick).toHaveBeenCalledWith(expect.objectContaining({ type: 'click' }));
225
+
226
+ // Reset call history
227
+ spies?.methods.handleClick.mockClear();
228
+ ```
229
+
230
+ #### Method Mocking
231
+
232
+ Replace methods with pre-configured mocks:
233
+
234
+ ```tsx
235
+ // Create mock with desired return value *before* render
236
+ const fetchUserMock = vi.fn().mockResolvedValue({
237
+ id: '123',
238
+ name: 'Test User',
239
+ email: 'test@example.com',
240
+ });
241
+
242
+ // Mock is applied before initialisation
243
+ const { root, spies, waitForChanges } = await render(<user-profile userId="123" />, {
244
+ spyOn: {
245
+ mocks: { fetchUserData: fetchUserMock },
246
+ },
247
+ });
248
+ await waitForChanges();
249
+
250
+ expect(fetchUserMock).toHaveBeenCalledWith('123');
251
+ expect(root.shadowRoot?.querySelector('.name')?.textContent).toBe('Test User');
252
+ ```
253
+
254
+ Access the original implementation to augment rather than fully replace:
255
+
256
+ ```tsx
257
+ const fetchMock = vi.fn();
258
+ const { spies } = await render(<my-component />, {
259
+ spyOn: { mocks: { fetchData: fetchMock } },
260
+ });
261
+
262
+ // Wrap the original to add logging or modify behaviour
263
+ fetchMock.mockImplementation(async (...args) => {
264
+ console.log('Fetching data with args:', args);
265
+ const result = await spies?.mocks.fetchData.original?.(...args);
266
+ console.log('Got result:', result);
267
+ return result;
268
+ });
269
+ ```
270
+
271
+ #### Prop Spying
272
+
273
+ Track when props are changed:
274
+
275
+ ```tsx
276
+ const { spies, setProps, waitForChanges } = await render(<my-button variant="primary">Click me</my-button>, {
277
+ spyOn: {
278
+ props: ['variant', 'disabled'],
279
+ },
280
+ });
281
+
282
+ await setProps({ variant: 'danger' });
283
+ await waitForChanges();
284
+
285
+ expect(spies?.props.variant).toHaveBeenCalledWith('danger');
286
+ expect(spies?.props.variant).toHaveBeenCalledTimes(1);
287
+ ```
288
+
289
+ #### Lifecycle Spying
290
+
291
+ Spy on lifecycle methods. Methods that don't exist on the component are auto-stubbed:
292
+
293
+ ```tsx
294
+ const { spies, setProps, waitForChanges } = await render(<my-button>Click me</my-button>, {
295
+ spyOn: {
296
+ lifecycle: ['componentWillLoad', 'componentDidLoad', 'componentWillRender', 'componentDidRender'],
297
+ },
298
+ });
299
+
300
+ // Lifecycle methods are called during initial render
301
+ expect(spies?.lifecycle.componentWillLoad).toHaveBeenCalledTimes(1);
302
+ expect(spies?.lifecycle.componentDidRender).toHaveBeenCalledTimes(1);
303
+
304
+ // Trigger a re-render
305
+ await setProps({ variant: 'danger' });
306
+ await waitForChanges();
307
+
308
+ // Re-render lifecycle methods called again
309
+ expect(spies?.lifecycle.componentWillRender).toHaveBeenCalledTimes(2);
310
+ expect(spies?.lifecycle.componentDidRender).toHaveBeenCalledTimes(2);
311
+ ```
312
+
313
+ #### Resetting Spies
314
+
315
+ Reset all spies at once using `resetAll()`. This clears call histories AND resets mock implementations:
316
+
317
+ ```tsx
318
+ const fetchMock = vi.fn().mockReturnValue('mocked');
319
+ const { root, spies, setProps, waitForChanges } = await render(<my-button variant="primary">Click me</my-button>, {
320
+ spyOn: {
321
+ methods: ['handleClick'],
322
+ mocks: { fetchData: fetchMock },
323
+ props: ['variant'],
324
+ },
325
+ });
326
+
327
+ // Trigger some calls
328
+ root.shadowRoot?.querySelector('button')?.click();
329
+ await setProps({ variant: 'danger' });
330
+
331
+ // Reset everything
332
+ spies?.resetAll();
333
+
334
+ // Call histories cleared
335
+ expect(spies?.methods.handleClick).toHaveBeenCalledTimes(0);
336
+ expect(spies?.props.variant).toHaveBeenCalledTimes(0);
337
+
338
+ // Mock implementations reset to default (returns undefined)
339
+ expect(fetchMock()).toBeUndefined();
340
+ ```
341
+
342
+ #### Nested Components
343
+
344
+ When the root element is not a custom element, or when you have multiple custom elements, use `getComponentSpies()` to retrieve spies for specific elements:
345
+
346
+ ```tsx
347
+ import { render, getComponentSpies, h } from '@stencil/vitest';
348
+
349
+ // Root is a div, not a custom element
350
+ const { root } = await render(
351
+ <div>
352
+ <my-button>Click me</my-button>
353
+ </div>,
354
+ {
355
+ spyOn: { methods: ['handleClick'] },
356
+ },
357
+ );
358
+
359
+ // Query the nested custom element
360
+ const button = root.querySelector('my-button') as HTMLElement;
361
+
362
+ // Get spies for the nested element
363
+ const buttonSpies = getComponentSpies(button);
364
+ expect(buttonSpies?.methods.handleClick).toBeDefined();
365
+
366
+ // Multiple instances have independent spies
367
+ const { root: container } = await render(
368
+ <div>
369
+ <my-button class="a">A</my-button>
370
+ <my-button class="b">B</my-button>
371
+ </div>,
372
+ { spyOn: { methods: ['handleClick'] } },
373
+ );
374
+
375
+ const spiesA = getComponentSpies(container.querySelector('.a') as HTMLElement);
376
+ const spiesB = getComponentSpies(container.querySelector('.b') as HTMLElement);
377
+
378
+ // Each has its own spy instance
379
+ container.querySelector('.a')?.shadowRoot?.querySelector('button')?.click();
380
+ expect(spiesA?.methods.handleClick).toHaveBeenCalledTimes(1);
381
+ expect(spiesB?.methods.handleClick).toHaveBeenCalledTimes(0);
382
+ ```
383
+
384
+ #### Per-Component Configurations
385
+
386
+ When rendering multiple component types, use the `components` property for tag-specific spy configs:
387
+
388
+ ```tsx
389
+ import { render, getComponentSpies, h } from '@stencil/vitest';
390
+
391
+ const { root } = await render(
392
+ <my-card cardTitle="Test">
393
+ <my-button slot="footer">Click me</my-button>
394
+ </my-card>,
395
+ {
396
+ spyOn: {
397
+ lifecycle: ['componentDidLoad'], // base - applies to all
398
+ components: {
399
+ 'my-card': { props: ['cardTitle'] },
400
+ 'my-button': { methods: ['handleClick'] },
401
+ },
402
+ },
403
+ },
404
+ );
405
+
406
+ const cardSpies = getComponentSpies(root);
407
+ const buttonSpies = getComponentSpies(root.querySelector('my-button') as HTMLElement);
408
+
409
+ // Both get base lifecycle spy + their specific config
410
+ expect(cardSpies?.lifecycle.componentDidLoad).toHaveBeenCalled();
411
+ expect(cardSpies?.props.cardTitle).toBeDefined();
412
+
413
+ expect(buttonSpies?.lifecycle.componentDidLoad).toHaveBeenCalled();
414
+ expect(buttonSpies?.methods.handleClick).toBeDefined();
415
+ ```
416
+
180
417
  ### Event Testing
181
418
 
182
419
  Test custom events emitted by your components:
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAOlE,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAMtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,GAAE,cAAc,GAAG;IAAE,aAAa,CAAC,EAAE,MAAM,GAAG,aAAa,CAAA;CAAO,GACvE,OAAO,CAAC,cAAc,CAAC,CAqBzB"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAQlE,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAMtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,GAAE,cAAc,GAAG;IAAE,aAAa,CAAC,EAAE,MAAM,GAAG,aAAa,CAAA;CAAO,GACvE,OAAO,CAAC,cAAc,CAAC,CAqBzB"}
package/dist/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { fileURLToPath } from 'node:url';
2
2
  import { defineConfig } from 'vitest/config';
3
- import { loadStencilConfig, getStencilSrcDir, getStencilOutputDirs, getStencilResolveAliases, } from './setup/config-loader.js';
3
+ import { loadStencilConfig, getStencilSrcDir, getStencilOutputDirs, getStencilResolveAliases, getStencilHydratedFlag, } from './setup/config-loader.js';
4
4
  // Resolve the path to the stencil environment module at config load time
5
5
  // This is necessary for pnpm which doesn't hoist transitive dependencies
6
6
  const stencilEnvironmentPath = fileURLToPath(import.meta.resolve('@stencil/vitest/environments/stencil'));
@@ -130,6 +130,7 @@ function applyStencilDefaults(config, stencilConfig) {
130
130
  __STENCIL_PROD__: JSON.stringify(process.env.STENCIL_PROD === 'true'),
131
131
  __STENCIL_SERVE__: JSON.stringify(process.env.STENCIL_SERVE === 'true'),
132
132
  __STENCIL_PORT__: JSON.stringify(process.env.STENCIL_PORT || ''),
133
+ __STENCIL_HYDRATED_FLAG__: JSON.stringify(getStencilHydratedFlag(stencilConfig)),
133
134
  };
134
135
  if (!result.define) {
135
136
  result.define = stencilEnvDefines;
@@ -342,6 +343,7 @@ function enhanceProject(project, stencilConfig) {
342
343
  __STENCIL_PROD__: JSON.stringify(process.env.STENCIL_PROD === 'true'),
343
344
  __STENCIL_SERVE__: JSON.stringify(process.env.STENCIL_SERVE === 'true'),
344
345
  __STENCIL_PORT__: JSON.stringify(process.env.STENCIL_PORT || ''),
346
+ __STENCIL_HYDRATED_FLAG__: JSON.stringify(getStencilHydratedFlag(stencilConfig)),
345
347
  };
346
348
  if (!enhanced.define) {
347
349
  enhanced.define = stencilEnvDefines;
package/dist/core.d.ts CHANGED
@@ -4,4 +4,6 @@ export { h } from '@stencil/core';
4
4
  export { render, waitForStable, waitForExist } from './testing/render.js';
5
5
  export { serializeHtml, prettifyHtml, SerializeOptions } from './testing/html-serializer.js';
6
6
  export type { RenderOptions, RenderResult } from './types.js';
7
+ export { getComponentSpies, clearComponentSpies } from './testing/spy-helper.js';
8
+ export type { SpyConfig, ComponentSpies } from './testing/spy-helper.js';
7
9
  //# sourceMappingURL=core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AACA,OAAO,uBAAuB,CAAC;AAC/B,OAAO,kCAAkC,CAAC;AAE1C,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAC7F,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AACA,OAAO,uBAAuB,CAAC;AAC/B,OAAO,kCAAkC,CAAC;AAE1C,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAC7F,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACjF,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/core.js CHANGED
@@ -4,3 +4,4 @@ import './testing/snapshot-serializer.js';
4
4
  export { h } from '@stencil/core';
5
5
  export { render, waitForStable, waitForExist } from './testing/render.js';
6
6
  export { serializeHtml, prettifyHtml } from './testing/html-serializer.js';
7
+ export { getComponentSpies, clearComponentSpies } from './testing/spy-helper.js';
package/dist/globals.d.ts CHANGED
@@ -21,3 +21,13 @@
21
21
  declare const __STENCIL_PROD__: boolean;
22
22
  declare const __STENCIL_SERVE__: boolean;
23
23
  declare const __STENCIL_PORT__: string;
24
+
25
+ interface StencilHydratedFlag {
26
+ name: string;
27
+ selector: 'class' | 'attribute';
28
+ property: string;
29
+ initialValue: string;
30
+ hydratedValue: string;
31
+ }
32
+
33
+ declare const __STENCIL_HYDRATED_FLAG__: StencilHydratedFlag | null;
@@ -1,4 +1,4 @@
1
- import type { Config as StencilConfig } from '@stencil/core/internal';
1
+ import type { Config as StencilConfig, HydratedFlag } from '@stencil/core/internal';
2
2
  /**
3
3
  * Load Stencil configuration from a file path
4
4
  * Uses jiti to handle TypeScript files in Node.js
@@ -12,6 +12,12 @@ export declare function getStencilSrcDir(config?: StencilConfig): string;
12
12
  * Get all output directories from Stencil config for exclusion
13
13
  */
14
14
  export declare function getStencilOutputDirs(config?: StencilConfig): string[];
15
+ /**
16
+ * Get the hydrated flag configuration from Stencil config.
17
+ * Returns null if hydration indication is explicitly disabled.
18
+ * Returns defaults if not configured.
19
+ */
20
+ export declare function getStencilHydratedFlag(config?: StencilConfig): HydratedFlag | null;
15
21
  /**
16
22
  * Create resolve aliases from Stencil config
17
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/setup/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"}
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/setup/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAIpF;;;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;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,YAAY,GAAG,IAAI,CAclF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBvF"}
@@ -70,6 +70,26 @@ export function getStencilOutputDirs(config) {
70
70
  const validDirs = Array.from(outputDirs).filter((dir) => !dir.includes('..'));
71
71
  return validDirs.length > 0 ? validDirs : ['dist', 'www', 'build', '.stencil'];
72
72
  }
73
+ /**
74
+ * Get the hydrated flag configuration from Stencil config.
75
+ * Returns null if hydration indication is explicitly disabled.
76
+ * Returns defaults if not configured.
77
+ */
78
+ export function getStencilHydratedFlag(config) {
79
+ // If explicitly null, hydration indication is disabled
80
+ if (config?.hydratedFlag === null) {
81
+ return null;
82
+ }
83
+ // If undefined or object, return with defaults applied
84
+ const flag = config?.hydratedFlag;
85
+ return {
86
+ name: flag?.name ?? 'hydrated',
87
+ selector: flag?.selector ?? 'class',
88
+ property: flag?.property ?? 'visibility',
89
+ initialValue: flag?.initialValue ?? 'hidden',
90
+ hydratedValue: flag?.hydratedValue ?? 'inherit',
91
+ };
92
+ }
73
93
  /**
74
94
  * Create resolve aliases from Stencil config
75
95
  */
@@ -1,4 +1,5 @@
1
1
  import type { RenderResult } from '../types.js';
2
+ import { type SpyConfig } from './spy-helper.js';
2
3
  interface RenderOptions {
3
4
  /**
4
5
  * Whether to clear existing stage containers before rendering. Defaults to true.
@@ -14,6 +15,10 @@ interface RenderOptions {
14
15
  * Defaults to true.
15
16
  */
16
17
  waitForReady?: boolean;
18
+ /**
19
+ * Spy configuration for this render call. Spies on methods, props, and lifecycle hooks.
20
+ */
21
+ spyOn?: SpyConfig;
17
22
  }
18
23
  /**
19
24
  * Poll until element has dimensions (is rendered/visible in real browser).
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/testing/render.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,aAAa,CAAC;AAE1D,UAAU,aAAa;IACrB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAyBD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,iBAAiB,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAyCtG;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAAO,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAY5F;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,CAAC,GAAG,GAAG,EACvE,QAAQ,EAAE,GAAG,GAAG,MAAM,EACtB,OAAO,GAAE,aAGR,GACA,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAkJ7B"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/testing/render.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAyC,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAExF,UAAU,aAAa;IACrB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AA+FD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,iBAAiB,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAyCtG;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAAO,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAY5F;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,CAAC,GAAG,GAAG,EACvE,QAAQ,EAAE,GAAG,GAAG,MAAM,EACtB,OAAO,GAAE,aAGR,GACA,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAqK7B"}
@@ -1,4 +1,5 @@
1
1
  import { render as stencilRender } from '@stencil/core';
2
+ import { setRenderSpyConfig, getComponentSpies } from './spy-helper.js';
2
3
  // Track event spies
3
4
  const eventSpies = new WeakMap();
4
5
  /**
@@ -21,6 +22,66 @@ function isRealBrowser() {
21
22
  }
22
23
  return true;
23
24
  }
25
+ /**
26
+ * Get the hydrated flag config, with defaults when not configured.
27
+ * Returns null if hydration is explicitly disabled.
28
+ */
29
+ function getHydratedFlag() {
30
+ // If global is defined, use it (could be null if explicitly disabled)
31
+ if (typeof __STENCIL_HYDRATED_FLAG__ !== 'undefined') {
32
+ return __STENCIL_HYDRATED_FLAG__;
33
+ }
34
+ // Default to 'hydrated' class when no config loaded
35
+ return { name: 'hydrated', selector: 'class' };
36
+ }
37
+ /**
38
+ * Find the first custom element (tag contains '-') in the tree.
39
+ * If the element itself is a custom element, returns it.
40
+ * Otherwise walks down to find the topmost custom element child.
41
+ */
42
+ function findCustomElement(element) {
43
+ if (element.tagName.includes('-')) {
44
+ return element;
45
+ }
46
+ // Breadth-first search for first custom element
47
+ const queue = Array.from(element.children);
48
+ while (queue.length > 0) {
49
+ const child = queue.shift();
50
+ if (child.tagName.includes('-')) {
51
+ return child;
52
+ }
53
+ queue.push(...Array.from(child.children));
54
+ }
55
+ return null;
56
+ }
57
+ /**
58
+ * Wait for element to be hydrated based on Stencil's hydrated flag config.
59
+ * Checks for the hydrated class or attribute as configured.
60
+ * Returns immediately if hydration is disabled (flag is null).
61
+ */
62
+ async function waitForHydrated(element, timeout = 5000) {
63
+ const flag = getHydratedFlag();
64
+ // If hydration is disabled, skip this check
65
+ if (flag === null) {
66
+ return;
67
+ }
68
+ // Find the custom element to check - hydrated flag is applied to the component, not wrappers
69
+ const customElement = findCustomElement(element);
70
+ if (!customElement) {
71
+ return;
72
+ }
73
+ const start = Date.now();
74
+ while (Date.now() - start < timeout) {
75
+ const isHydrated = flag.selector === 'attribute'
76
+ ? customElement.hasAttribute(flag.name)
77
+ : customElement.classList.contains(flag.name);
78
+ if (isHydrated) {
79
+ return;
80
+ }
81
+ await new Promise((r) => requestAnimationFrame(r));
82
+ }
83
+ // Don't throw - component might not use hydration or might be ready without flag
84
+ }
24
85
  /**
25
86
  * Poll until element has dimensions (is rendered/visible in real browser).
26
87
  * Accepts either an Element or a CSS selector string.
@@ -94,6 +155,10 @@ export async function render(template, options = {
94
155
  existingStages.forEach((stage) => stage.remove());
95
156
  }
96
157
  document.body.appendChild(container);
158
+ // Set per-render spy config before element creation
159
+ if (options.spyOn) {
160
+ setRenderSpyConfig(options.spyOn);
161
+ }
97
162
  if (typeof template === 'string') {
98
163
  // Handle string template - add as innerHTML
99
164
  container.innerHTML = template;
@@ -107,10 +172,19 @@ export async function render(template, options = {
107
172
  if (!element) {
108
173
  throw new Error('Failed to render component');
109
174
  }
175
+ // Wait for custom element to be defined
176
+ const tagName = element.tagName.toLowerCase();
177
+ if (tagName.includes('-')) {
178
+ await customElements.whenDefined(tagName);
179
+ }
110
180
  // Wait for component to be ready
111
181
  if (typeof element.componentOnReady === 'function') {
112
182
  await element.componentOnReady();
113
183
  }
184
+ // Clear per-render spy config after component is ready
185
+ if (options.spyOn) {
186
+ setRenderSpyConfig(null);
187
+ }
114
188
  // Define waitForChanges first so we can use it in the ready check
115
189
  function waitForChanges(documentElement = element) {
116
190
  return new Promise((resolve) => {
@@ -145,10 +219,8 @@ export async function render(template, options = {
145
219
  }
146
220
  // Wait for component to be fully rendered if requested (default: true)
147
221
  if (options.waitForReady !== false) {
148
- if (isRealBrowser()) {
149
- // In real browser, poll until element has dimensions
150
- await waitForStable(element);
151
- }
222
+ // Wait for Stencil's hydration flag (skipped if hydration disabled)
223
+ await waitForHydrated(element);
152
224
  // Always wait for Stencil's update cycle to complete
153
225
  await waitForChanges();
154
226
  }
@@ -206,6 +278,8 @@ export async function render(template, options = {
206
278
  if (element.__stencil__getHostRef) {
207
279
  instance = element.__stencil__getHostRef()?.$lazyInstance$ || element;
208
280
  }
281
+ // Get spies if spyOn option was used
282
+ const spies = options.spyOn ? getComponentSpies(element) : undefined;
209
283
  return {
210
284
  root: element,
211
285
  waitForChanges,
@@ -213,5 +287,6 @@ export async function render(template, options = {
213
287
  setProps,
214
288
  unmount,
215
289
  spyOnEvent,
290
+ spies,
216
291
  };
217
292
  }
@@ -0,0 +1,123 @@
1
+ import { type Mock } from 'vitest';
2
+ /**
3
+ * Base configuration for what to spy on in a component
4
+ */
5
+ interface SpyConfigBase {
6
+ /**
7
+ * Method names to spy on (calls original implementation)
8
+ */
9
+ methods?: string[];
10
+ /**
11
+ * Pre-configured mocks to replace methods. The mock is applied before lifecycle runs,
12
+ * allowing you to control return values for methods called during initialization.
13
+ * @example
14
+ * ```ts
15
+ * const loadUserMock = vi.fn().mockResolvedValue({ id: 1, name: 'Test' });
16
+ * const { root } = await render(<my-component />, {
17
+ * spyOn: { mocks: { loadUser: loadUserMock } }
18
+ * });
19
+ * expect(loadUserMock).toHaveBeenCalled();
20
+ * ```
21
+ */
22
+ mocks?: Record<string, Mock>;
23
+ /**
24
+ * Property names to spy on
25
+ */
26
+ props?: string[];
27
+ /**
28
+ * Lifecycle method names to spy on ('componentWillLoad', 'componentDidRender')
29
+ */
30
+ lifecycle?: string[];
31
+ }
32
+ /**
33
+ * Configuration for what to spy on in a component.
34
+ * Can include per-component overrides via the `components` property.
35
+ */
36
+ export interface SpyConfig extends SpyConfigBase {
37
+ /**
38
+ * Per-component spy configurations, keyed by tag name.
39
+ * These override the base config for specific components.
40
+ * @example
41
+ * ```ts
42
+ * spyOn: {
43
+ * lifecycle: ['componentDidLoad'], // applies to all
44
+ * components: {
45
+ * 'my-select': { methods: ['open', 'close'] },
46
+ * 'my-option': { methods: ['select'] },
47
+ * }
48
+ * }
49
+ * ```
50
+ */
51
+ components?: Record<string, SpyConfigBase>;
52
+ }
53
+ /**
54
+ * A mock with access to the original implementation
55
+ */
56
+ interface MockWithOriginal extends Mock {
57
+ /**
58
+ * The original method implementation, bound to the component instance.
59
+ * Call this within mockImplementation to augment rather than replace.
60
+ */
61
+ original?: (...args: any[]) => any;
62
+ }
63
+ /**
64
+ * Container for all spies on a component instance
65
+ */
66
+ export interface ComponentSpies {
67
+ /**
68
+ * Spies on component methods (calls through to original)
69
+ */
70
+ methods: Record<string, Mock>;
71
+ /**
72
+ * Mocks on component methods (pure stubs, doesn't call original).
73
+ * Each mock has an `original` property to access the original implementation.
74
+ */
75
+ mocks: Record<string, MockWithOriginal>;
76
+ /**
77
+ * Spies on property setters
78
+ */
79
+ props: Record<string, Mock>;
80
+ /**
81
+ * Spies on lifecycle methods
82
+ */
83
+ lifecycle: Record<string, Mock>;
84
+ /**
85
+ * The target instance (either $lazyInstance$ or the element itself for custom-elements output)
86
+ */
87
+ instance: any;
88
+ /**
89
+ * Reset all spies/mocks - clears call history AND resets implementations to default.
90
+ */
91
+ resetAll: () => void;
92
+ }
93
+ /**
94
+ * Set spy config for the next render call. Used internally by render().
95
+ * @internal
96
+ */
97
+ export declare function setRenderSpyConfig(config: SpyConfig | null): void;
98
+ /**
99
+ * Get the spies for a rendered component instance.
100
+ * Spies are lazily applied on first call to ensure the instance is fully constructed.
101
+ *
102
+ * @param element - The rendered component element
103
+ * @returns The spies object or undefined if no spies were registered
104
+ */
105
+ export declare function getComponentSpies(element: HTMLElement): ComponentSpies | undefined;
106
+ /**
107
+ * Clear spy registrations. Call this in afterEach to reset state between tests.
108
+ *
109
+ * @param tagName - Optional tag name to clear. If omitted, clears all registrations.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * afterEach(() => {
114
+ * clearComponentSpies(); // Clear all
115
+ * });
116
+ *
117
+ * // Or clear specific component
118
+ * clearComponentSpies('my-button');
119
+ * ```
120
+ */
121
+ export declare function clearComponentSpies(tagName?: string): void;
122
+ export {};
123
+ //# sourceMappingURL=spy-helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spy-helper.d.ts","sourceRoot":"","sources":["../../src/testing/spy-helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAM,KAAK,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEvC;;GAEG;AACH,UAAU,aAAa;IACrB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC7B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAU,SAAQ,aAAa;IAC9C;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC5C;AAED;;GAEG;AACH,UAAU,gBAAiB,SAAQ,IAAI;IACrC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC9B;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACxC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC5B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAChC;;OAEG;IACH,QAAQ,EAAE,GAAG,CAAC;IACd;;OAEG;IACH,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAgDD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAEjE;AAmLD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,CAkBlF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ1D"}
@@ -0,0 +1,256 @@
1
+ import { vi } from 'vitest';
2
+ // Registry of components to spy on (by tag name)
3
+ const spyTargets = {};
4
+ // Store spies by element reference (after applied)
5
+ const elementSpies = new WeakMap();
6
+ // Store pending spy configs by element (before applied)
7
+ const pendingSpies = new WeakMap();
8
+ // Per-render spy config (set by render(), consumed by constructor)
9
+ let pendingRenderConfig = null;
10
+ /**
11
+ * Resolve the spy config for a specific tag, merging base config with per-component overrides.
12
+ */
13
+ function resolveConfigForTag(config, tagName) {
14
+ const tagLower = tagName.toLowerCase();
15
+ const tagConfig = config.components?.[tagLower];
16
+ // Extract base config (everything except `components`)
17
+ const { components: _components, ...baseConfig } = config;
18
+ const hasBaseConfig = baseConfig.methods?.length ||
19
+ (baseConfig.mocks && Object.keys(baseConfig.mocks).length) ||
20
+ baseConfig.props?.length ||
21
+ baseConfig.lifecycle?.length;
22
+ if (!tagConfig && !hasBaseConfig) {
23
+ return null; // No config applies to this tag
24
+ }
25
+ if (!tagConfig) {
26
+ return baseConfig; // Only base config
27
+ }
28
+ if (!hasBaseConfig) {
29
+ return tagConfig; // Only tag-specific config
30
+ }
31
+ // Merge: tag-specific config extends base config
32
+ return {
33
+ methods: [...(baseConfig.methods || []), ...(tagConfig.methods || [])],
34
+ mocks: { ...(baseConfig.mocks || {}), ...(tagConfig.mocks || {}) },
35
+ props: [...(baseConfig.props || []), ...(tagConfig.props || [])],
36
+ lifecycle: [...(baseConfig.lifecycle || []), ...(tagConfig.lifecycle || [])],
37
+ };
38
+ }
39
+ /**
40
+ * Set spy config for the next render call. Used internally by render().
41
+ * @internal
42
+ */
43
+ export function setRenderSpyConfig(config) {
44
+ pendingRenderConfig = config;
45
+ }
46
+ // Store original define before patching
47
+ const origDefine = customElements.define.bind(customElements);
48
+ /**
49
+ * Apply spies to a target instance (shared logic)
50
+ */
51
+ function applySpies(target, config) {
52
+ const spies = {
53
+ methods: {},
54
+ mocks: {},
55
+ props: {},
56
+ lifecycle: {},
57
+ instance: target,
58
+ resetAll() {
59
+ // Reset all method spies
60
+ for (const spy of Object.values(this.methods)) {
61
+ spy.mockReset();
62
+ }
63
+ // Reset all mocks
64
+ for (const mock of Object.values(this.mocks)) {
65
+ mock.mockReset();
66
+ }
67
+ // Reset all prop spies
68
+ for (const spy of Object.values(this.props)) {
69
+ spy.mockReset();
70
+ }
71
+ // Reset all lifecycle spies
72
+ for (const spy of Object.values(this.lifecycle)) {
73
+ spy.mockReset();
74
+ }
75
+ },
76
+ };
77
+ // Spy on methods (calls through to original)
78
+ if (config.methods) {
79
+ for (const methodName of config.methods) {
80
+ const method = target[methodName];
81
+ if (typeof method === 'function' && !method.__isSpy) {
82
+ const spy = vi.fn((...args) => method.apply(target, args));
83
+ spy.__isSpy = true;
84
+ spies.methods[methodName] = spy;
85
+ Object.defineProperty(target, methodName, {
86
+ value: spy,
87
+ writable: true,
88
+ configurable: true,
89
+ });
90
+ }
91
+ }
92
+ }
93
+ // Mock methods (use pre-configured mocks, original is accessible)
94
+ if (config.mocks) {
95
+ for (const [methodName, mock] of Object.entries(config.mocks)) {
96
+ const original = target[methodName];
97
+ const mockWithOriginal = mock;
98
+ mockWithOriginal.__isSpy = true;
99
+ // Store the original so users can call it if they want to augment rather than replace
100
+ if (typeof original === 'function') {
101
+ mockWithOriginal.original = (...args) => original.apply(target, args);
102
+ }
103
+ spies.mocks[methodName] = mockWithOriginal;
104
+ Object.defineProperty(target, methodName, {
105
+ value: mockWithOriginal,
106
+ writable: true,
107
+ configurable: true,
108
+ });
109
+ }
110
+ }
111
+ // Spy on props - need to intercept $instanceValues$ since Stencil stores props there
112
+ if (config.props) {
113
+ const hostRef = target.__stencil__getHostRef?.();
114
+ const instanceValues = hostRef?.$instanceValues$;
115
+ if (instanceValues) {
116
+ // Wrap the Map's set method to intercept prop changes
117
+ const originalSet = instanceValues.set.bind(instanceValues);
118
+ instanceValues.set = (key, value) => {
119
+ const result = originalSet(key, value);
120
+ // If this prop is being spied on, call the spy
121
+ if (config.props.includes(key) && spies.props[key]) {
122
+ spies.props[key](value);
123
+ }
124
+ return result;
125
+ };
126
+ }
127
+ // Create spies for each prop
128
+ for (const propName of config.props) {
129
+ const spy = vi.fn();
130
+ spies.props[propName] = spy;
131
+ }
132
+ }
133
+ // Spy on lifecycle methods (auto-stub if not defined)
134
+ if (config.lifecycle) {
135
+ for (const lifecycleName of config.lifecycle) {
136
+ const method = target[lifecycleName];
137
+ let spy;
138
+ if (typeof method === 'function' && !method.__isSpy) {
139
+ // Method exists - wrap it
140
+ spy = vi.fn((...args) => method.apply(target, args));
141
+ }
142
+ else if (typeof method !== 'function') {
143
+ // Method doesn't exist - create stub so Stencil will call it
144
+ spy = vi.fn();
145
+ }
146
+ else {
147
+ // Already a spy
148
+ continue;
149
+ }
150
+ spy.__isSpy = true;
151
+ spies.lifecycle[lifecycleName] = spy;
152
+ Object.defineProperty(target, lifecycleName, {
153
+ value: spy,
154
+ writable: true,
155
+ configurable: true,
156
+ });
157
+ }
158
+ }
159
+ return spies;
160
+ }
161
+ // Patch customElements.define to intercept component registration
162
+ customElements.define = function (name, ctor, options) {
163
+ const lc = name.toLowerCase();
164
+ const OrigCtor = ctor;
165
+ // Wrap ALL components to enable per-render spies without module-level registration
166
+ const Wrapped = class extends OrigCtor {
167
+ constructor(...args) {
168
+ super(...args);
169
+ // Check for spy config: per-render takes priority, then module-level
170
+ // Capture config now, at constructor time (before async callbacks)
171
+ const baseConfig = pendingRenderConfig || spyTargets[lc];
172
+ if (!baseConfig)
173
+ return; // No spying configured, quick exit
174
+ // Resolve config for this specific tag (handles per-component overrides)
175
+ const configToUse = resolveConfigForTag(baseConfig, lc);
176
+ if (!configToUse)
177
+ return; // No config applies to this tag
178
+ // After super(), registerHost has run and we have access to hostRef
179
+ const hostRef = this.__stencil__getHostRef?.();
180
+ if (hostRef && hostRef.$fetchedCbList$) {
181
+ // Lazy-load path: Use $fetchedCbList$ to apply spies after constructor but before render
182
+ const element = this;
183
+ // Capture config in closure for when callback executes
184
+ const capturedConfig = configToUse;
185
+ hostRef.$fetchedCbList$.push(() => {
186
+ const instance = hostRef.$lazyInstance$;
187
+ if (instance) {
188
+ const spies = applySpies(instance, capturedConfig);
189
+ elementSpies.set(element, spies);
190
+ }
191
+ });
192
+ }
193
+ else if (hostRef) {
194
+ // Custom-elements output with Stencil runtime: element IS the instance
195
+ // Apply spies immediately since there's no lazy loading
196
+ const spies = applySpies(this, configToUse);
197
+ elementSpies.set(this, spies);
198
+ }
199
+ else {
200
+ // Custom-elements output path: element IS the instance
201
+ const spies = applySpies(this, configToUse);
202
+ elementSpies.set(this, spies);
203
+ }
204
+ }
205
+ };
206
+ return origDefine.call(customElements, name, Wrapped, options);
207
+ };
208
+ /**
209
+ * Get the spies for a rendered component instance.
210
+ * Spies are lazily applied on first call to ensure the instance is fully constructed.
211
+ *
212
+ * @param element - The rendered component element
213
+ * @returns The spies object or undefined if no spies were registered
214
+ */
215
+ export function getComponentSpies(element) {
216
+ // Return existing spies if already applied
217
+ const existing = elementSpies.get(element);
218
+ if (existing) {
219
+ return existing;
220
+ }
221
+ // Check for pending spy config
222
+ const pending = pendingSpies.get(element);
223
+ if (pending) {
224
+ // Apply spies now that the instance is fully constructed
225
+ const spies = applySpies(pending.instance, pending.config);
226
+ elementSpies.set(element, spies);
227
+ pendingSpies.delete(element);
228
+ return spies;
229
+ }
230
+ return undefined;
231
+ }
232
+ /**
233
+ * Clear spy registrations. Call this in afterEach to reset state between tests.
234
+ *
235
+ * @param tagName - Optional tag name to clear. If omitted, clears all registrations.
236
+ *
237
+ * @example
238
+ * ```ts
239
+ * afterEach(() => {
240
+ * clearComponentSpies(); // Clear all
241
+ * });
242
+ *
243
+ * // Or clear specific component
244
+ * clearComponentSpies('my-button');
245
+ * ```
246
+ */
247
+ export function clearComponentSpies(tagName) {
248
+ if (tagName) {
249
+ delete spyTargets[tagName.toLowerCase()];
250
+ }
251
+ else {
252
+ for (const key of Object.keys(spyTargets)) {
253
+ delete spyTargets[key];
254
+ }
255
+ }
256
+ }
package/dist/types.d.ts CHANGED
@@ -23,6 +23,7 @@ export interface EventSpy {
23
23
  */
24
24
  length: number;
25
25
  }
26
+ import type { SpyConfig } from './testing/spy-helper.js';
26
27
  /**
27
28
  * Component render options
28
29
  */
@@ -48,7 +49,13 @@ export interface RenderOptions {
48
49
  * Additional HTML attributes
49
50
  */
50
51
  attributes?: Record<string, string>;
52
+ /**
53
+ * Spy configuration for this render call. Spies on methods, props, and lifecycle hooks.
54
+ * Takes priority over module-level spyOnComponent() calls.
55
+ */
56
+ spyOn?: SpyConfig;
51
57
  }
58
+ import type { ComponentSpies } from './testing/spy-helper.js';
52
59
  /**
53
60
  * Render result for component testing
54
61
  */
@@ -78,5 +85,9 @@ export interface RenderResult<T = HTMLElement, I = any> {
78
85
  * Spy on a custom event
79
86
  */
80
87
  spyOnEvent: (eventName: string) => EventSpy;
88
+ /**
89
+ * Component spies (only present when `spyOn` option is used)
90
+ */
91
+ spies?: ComponentSpies;
81
92
  }
82
93
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,MAAM,EAAE,WAAW,EAAE,CAAC;IAEtB;;OAEG;IACH,UAAU,EAAE,WAAW,GAAG,SAAS,CAAC;IAEpC;;OAEG;IACH,SAAS,EAAE,WAAW,GAAG,SAAS,CAAC;IAEnC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE5B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC;IAE7C;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,WAAW,EAAE,CAAC,GAAG,GAAG;IACpD;;OAEG;IACH,IAAI,EAAE,CAAC,CAAC;IAER;;OAEG;IACH,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEb;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAExD;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB;;OAEG;IACH,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,QAAQ,CAAC;CAC7C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,MAAM,EAAE,WAAW,EAAE,CAAC;IAEtB;;OAEG;IACH,UAAU,EAAE,WAAW,GAAG,SAAS,CAAC;IAEpC;;OAEG;IACH,SAAS,EAAE,WAAW,GAAG,SAAS,CAAC;IAEnC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE5B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC;IAE7C;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEpC;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAED,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,WAAW,EAAE,CAAC,GAAG,GAAG;IACpD;;OAEG;IACH,IAAI,EAAE,CAAC,CAAC;IAER;;OAEG;IACH,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEb;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAExD;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB;;OAEG;IACH,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,QAAQ,CAAC;IAE5C;;OAEG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB"}
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.8.3",
7
+ "version": "1.9.0",
8
8
  "description": "First-class testing utilities for Stencil design systems with Vitest",
9
9
  "license": "MIT",
10
10
  "type": "module",
@@ -100,7 +100,7 @@
100
100
  "dependencies": {
101
101
  "jiti": "^2.6.1",
102
102
  "local-pkg": "^1.1.2",
103
- "vitest-environment-stencil": "1.8.3"
103
+ "vitest-environment-stencil": "1.9.0"
104
104
  },
105
105
  "devDependencies": {
106
106
  "@eslint/js": "^9.39.2",