@stencil/vitest 0.0.1-dev.20260109124515.90fb962

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.
Files changed (53) hide show
  1. package/README.md +296 -0
  2. package/dist/bin/stencil-test.d.ts +3 -0
  3. package/dist/bin/stencil-test.d.ts.map +1 -0
  4. package/dist/bin/stencil-test.js +341 -0
  5. package/dist/config.d.ts +73 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +302 -0
  8. package/dist/environments/env/happy-dom.d.ts +4 -0
  9. package/dist/environments/env/happy-dom.d.ts.map +1 -0
  10. package/dist/environments/env/happy-dom.js +15 -0
  11. package/dist/environments/env/jsdom.d.ts +4 -0
  12. package/dist/environments/env/jsdom.d.ts.map +1 -0
  13. package/dist/environments/env/jsdom.js +32 -0
  14. package/dist/environments/env/mock-doc.d.ts +4 -0
  15. package/dist/environments/env/mock-doc.d.ts.map +1 -0
  16. package/dist/environments/env/mock-doc.js +14 -0
  17. package/dist/environments/stencil.d.ts +28 -0
  18. package/dist/environments/stencil.d.ts.map +1 -0
  19. package/dist/environments/stencil.js +71 -0
  20. package/dist/environments/types.d.ts +6 -0
  21. package/dist/environments/types.d.ts.map +1 -0
  22. package/dist/environments/types.js +1 -0
  23. package/dist/index.d.ts +6 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +5 -0
  26. package/dist/setup/config-loader.d.ts +19 -0
  27. package/dist/setup/config-loader.d.ts.map +1 -0
  28. package/dist/setup/config-loader.js +92 -0
  29. package/dist/setup/happy-dom-setup.d.ts +12 -0
  30. package/dist/setup/happy-dom-setup.d.ts.map +1 -0
  31. package/dist/setup/happy-dom-setup.js +16 -0
  32. package/dist/setup/jsdom-setup.d.ts +30 -0
  33. package/dist/setup/jsdom-setup.d.ts.map +1 -0
  34. package/dist/setup/jsdom-setup.js +95 -0
  35. package/dist/setup/mock-doc-setup.d.ts +17 -0
  36. package/dist/setup/mock-doc-setup.d.ts.map +1 -0
  37. package/dist/setup/mock-doc-setup.js +90 -0
  38. package/dist/testing/html-serializer.d.ts +27 -0
  39. package/dist/testing/html-serializer.d.ts.map +1 -0
  40. package/dist/testing/html-serializer.js +152 -0
  41. package/dist/testing/matchers.d.ts +181 -0
  42. package/dist/testing/matchers.d.ts.map +1 -0
  43. package/dist/testing/matchers.js +460 -0
  44. package/dist/testing/render.d.ts +11 -0
  45. package/dist/testing/render.d.ts.map +1 -0
  46. package/dist/testing/render.js +118 -0
  47. package/dist/testing/snapshot-serializer.d.ts +17 -0
  48. package/dist/testing/snapshot-serializer.d.ts.map +1 -0
  49. package/dist/testing/snapshot-serializer.js +50 -0
  50. package/dist/types.d.ts +81 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +1 -0
  53. package/package.json +133 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Custom matchers for Stencil component testing
3
+ *
4
+ * These extend Vitest's expect with Stencil-specific assertions
5
+ */
6
+ import type { EventSpy } from '../types.js';
7
+ /**
8
+ * Custom matchers interface
9
+ */
10
+ interface CustomMatchers<R = unknown> {
11
+ toHaveClass(className: string): R;
12
+ toHaveClasses(classNames: string[]): R;
13
+ toMatchClasses(classNames: string[]): R;
14
+ toHaveAttribute(attribute: string, value?: string): R;
15
+ toEqualAttribute(attribute: string, value: string): R;
16
+ toEqualAttributes(expectedAttrs: Record<string, string>): R;
17
+ toHaveProperty(property: string, value?: any): R;
18
+ toHaveTextContent(text: string): R;
19
+ toEqualText(expectedText: string): R;
20
+ toBeVisible(): R;
21
+ toHaveShadowRoot(): R;
22
+ toEmitEvent(eventName: string): R;
23
+ toEqualHtml(expectedHtml: string): R;
24
+ toEqualLightHtml(expectedHtml: string): R;
25
+ toHaveReceivedEvent(): R;
26
+ toHaveReceivedEventTimes(count: number): R;
27
+ toHaveReceivedEventDetail(detail: any): R;
28
+ toHaveFirstReceivedEventDetail(detail: any): R;
29
+ toHaveLastReceivedEventDetail(detail: any): R;
30
+ toHaveNthReceivedEventDetail(index: number, detail: any): R;
31
+ }
32
+ declare global {
33
+ namespace Vi {
34
+ interface Assertion<T = any> extends CustomMatchers<T> {
35
+ }
36
+ interface AsymmetricMatchersContaining extends CustomMatchers {
37
+ }
38
+ }
39
+ }
40
+ /**
41
+ * Check if element has a class
42
+ */
43
+ export declare function toHaveClass(received: HTMLElement, className: string): {
44
+ pass: boolean;
45
+ message: () => string;
46
+ };
47
+ /**
48
+ * Check if element has multiple classes
49
+ * Checks if element has all of the specified CSS classes (order doesn't matter)
50
+ */
51
+ export declare function toHaveClasses(received: HTMLElement, classNames: string[]): {
52
+ pass: boolean;
53
+ message: () => string;
54
+ };
55
+ /**
56
+ * Check if element has exactly the specified CSS classes (no more, no less)
57
+ * Order doesn't matter, but the element must have exactly these classes
58
+ */
59
+ export declare function toMatchClasses(received: HTMLElement, classNames: string[]): {
60
+ pass: boolean;
61
+ message: () => string;
62
+ };
63
+ /**
64
+ * Check if element has an attribute
65
+ */
66
+ export declare function toHaveAttribute(received: HTMLElement, attribute: string, value?: string): {
67
+ pass: boolean;
68
+ message: () => string;
69
+ };
70
+ /**
71
+ * Check if element has a specific attribute with an exact value
72
+ */
73
+ export declare function toEqualAttribute(received: HTMLElement, attribute: string, value: string): {
74
+ pass: boolean;
75
+ message: () => string;
76
+ };
77
+ /**
78
+ * Check if element has all expected attributes with exact values
79
+ */
80
+ export declare function toEqualAttributes(received: HTMLElement, expectedAttrs: Record<string, string>): {
81
+ pass: boolean;
82
+ message: () => string;
83
+ };
84
+ /**
85
+ * Check if element has a property
86
+ */
87
+ export declare function toHaveProperty(received: any, property: string, value?: any): {
88
+ pass: boolean;
89
+ message: () => string;
90
+ };
91
+ /**
92
+ * Check if element has text content
93
+ */
94
+ export declare function toHaveTextContent(received: HTMLElement, text: string): {
95
+ pass: boolean;
96
+ message: () => string;
97
+ };
98
+ /**
99
+ * Check if element's text content exactly matches (after trimming)
100
+ */
101
+ export declare function toEqualText(received: HTMLElement, expectedText: string): {
102
+ pass: boolean;
103
+ message: () => string;
104
+ };
105
+ /**
106
+ * Check if element is visible
107
+ */
108
+ export declare function toBeVisible(received: HTMLElement): {
109
+ pass: boolean;
110
+ message: () => string;
111
+ };
112
+ /**
113
+ * Check if element has shadow root
114
+ */
115
+ export declare function toHaveShadowRoot(received: HTMLElement): {
116
+ pass: boolean;
117
+ message: () => string;
118
+ };
119
+ /**
120
+ * Custom matcher to check if an element's HTML matches the expected HTML
121
+ * Serializes the entire component tree including shadow DOM
122
+ */
123
+ export declare function toEqualHtml(received: string | HTMLElement | ShadowRoot, expected: string): {
124
+ pass: boolean;
125
+ message: () => string;
126
+ }; /**
127
+ * Custom matcher to check if an element's Light DOM matches the expected HTML
128
+ * Does not serialize shadow DOM
129
+ */
130
+ export declare function toEqualLightHtml(received: string | HTMLElement | ShadowRoot, expected: string): {
131
+ pass: boolean;
132
+ message: () => string;
133
+ };
134
+ /**
135
+ * Check if an EventSpy has received at least one event
136
+ */
137
+ export declare function toHaveReceivedEvent(received: EventSpy): {
138
+ pass: boolean;
139
+ message: () => string;
140
+ };
141
+ /**
142
+ * Check if an EventSpy has received an event a specific number of times
143
+ */
144
+ export declare function toHaveReceivedEventTimes(received: EventSpy, count: number): {
145
+ pass: boolean;
146
+ message: () => string;
147
+ };
148
+ /**
149
+ * Check if the last received event has the expected detail
150
+ */
151
+ export declare function toHaveReceivedEventDetail(received: EventSpy, detail: any): {
152
+ pass: boolean;
153
+ message: () => string;
154
+ };
155
+ /**
156
+ * Check if the first received event has the expected detail
157
+ */
158
+ export declare function toHaveFirstReceivedEventDetail(received: EventSpy, detail: any): {
159
+ pass: boolean;
160
+ message: () => string;
161
+ };
162
+ /**
163
+ * Check if the last received event has the expected detail (alias for toHaveReceivedEventDetail)
164
+ */
165
+ export declare function toHaveLastReceivedEventDetail(received: EventSpy, detail: any): {
166
+ pass: boolean;
167
+ message: () => string;
168
+ };
169
+ /**
170
+ * Check if the event at a specific index has the expected detail
171
+ */
172
+ export declare function toHaveNthReceivedEventDetail(received: EventSpy, index: number, detail: any): {
173
+ pass: boolean;
174
+ message: () => string;
175
+ };
176
+ /**
177
+ * Install custom matchers
178
+ */
179
+ export declare function installMatchers(): void;
180
+ export {};
181
+ //# sourceMappingURL=matchers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/testing/matchers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,460 @@
1
+ /**
2
+ * Custom matchers for Stencil component testing
3
+ *
4
+ * These extend Vitest's expect with Stencil-specific assertions
5
+ */
6
+ import { expect } from 'vitest';
7
+ import { serializeHtml, normalizeHtml, prettifyHtml } from './html-serializer.js';
8
+ /**
9
+ * Check if element has a class
10
+ */
11
+ export function toHaveClass(received, className) {
12
+ const pass = received.classList.contains(className);
13
+ return {
14
+ pass,
15
+ message: () => pass ? `Expected element not to have class "${className}"` : `Expected element to have class "${className}"`,
16
+ };
17
+ }
18
+ /**
19
+ * Check if element has multiple classes
20
+ * Checks if element has all of the specified CSS classes (order doesn't matter)
21
+ */
22
+ export function toHaveClasses(received, classNames) {
23
+ const missingClasses = [];
24
+ for (const className of classNames) {
25
+ if (!received.classList.contains(className)) {
26
+ missingClasses.push(className);
27
+ }
28
+ }
29
+ const pass = missingClasses.length === 0;
30
+ const actualClasses = Array.from(received.classList).join(', ');
31
+ return {
32
+ pass,
33
+ message: () => pass
34
+ ? `Expected element not to have classes [${classNames.join(', ')}]`
35
+ : `Expected element to have classes [${classNames.join(', ')}], but missing [${missingClasses.join(', ')}]. Actual classes: [${actualClasses}]`,
36
+ };
37
+ }
38
+ /**
39
+ * Check if element has exactly the specified CSS classes (no more, no less)
40
+ * Order doesn't matter, but the element must have exactly these classes
41
+ */
42
+ export function toMatchClasses(received, classNames) {
43
+ // Get classes from the class attribute to support mock-doc
44
+ const classAttr = received.getAttribute('class') || '';
45
+ const actualClasses = classAttr.split(/\s+/).filter(Boolean).sort();
46
+ const expectedClasses = [...classNames].filter(Boolean).sort();
47
+ const pass = actualClasses.length === expectedClasses.length && actualClasses.every((cls, idx) => cls === expectedClasses[idx]);
48
+ return {
49
+ pass,
50
+ message: () => pass
51
+ ? `Expected element not to have exactly classes [${classNames.join(', ')}]`
52
+ : `Expected element to have exactly classes [${classNames.join(', ')}], but got [${actualClasses.join(', ')}]`,
53
+ };
54
+ }
55
+ /**
56
+ * Check if element has an attribute
57
+ */
58
+ export function toHaveAttribute(received, attribute, value) {
59
+ const hasAttribute = received.hasAttribute(attribute);
60
+ if (!hasAttribute) {
61
+ return {
62
+ pass: false,
63
+ message: () => `Expected element to have attribute "${attribute}"`,
64
+ };
65
+ }
66
+ if (value !== undefined) {
67
+ const actualValue = received.getAttribute(attribute);
68
+ const pass = actualValue === value;
69
+ return {
70
+ pass,
71
+ message: () => pass
72
+ ? `Expected element not to have attribute "${attribute}" with value "${value}"`
73
+ : `Expected element to have attribute "${attribute}" with value "${value}", but got "${actualValue}"`,
74
+ };
75
+ }
76
+ return {
77
+ pass: true,
78
+ message: () => `Expected element not to have attribute "${attribute}"`,
79
+ };
80
+ }
81
+ /**
82
+ * Check if element has a specific attribute with an exact value
83
+ */
84
+ export function toEqualAttribute(received, attribute, value) {
85
+ const actualValue = received.getAttribute(attribute);
86
+ const pass = actualValue === value;
87
+ return {
88
+ pass,
89
+ message: () => pass
90
+ ? `Expected element attribute "${attribute}" not to equal "${value}"`
91
+ : `Expected element attribute "${attribute}" to equal "${value}", but got "${actualValue}"`,
92
+ };
93
+ }
94
+ /**
95
+ * Check if element has all expected attributes with exact values
96
+ */
97
+ export function toEqualAttributes(received, expectedAttrs) {
98
+ const mismatches = [];
99
+ const actualAttrs = {};
100
+ // Collect all actual attributes
101
+ for (let i = 0; i < received.attributes.length; i++) {
102
+ const attr = received.attributes[i];
103
+ actualAttrs[attr.name] = attr.value;
104
+ }
105
+ // Check expected attributes
106
+ for (const [name, expectedValue] of Object.entries(expectedAttrs)) {
107
+ const actualValue = received.getAttribute(name);
108
+ if (actualValue === null) {
109
+ mismatches.push(`missing "${name}"`);
110
+ }
111
+ else if (actualValue !== expectedValue) {
112
+ mismatches.push(`"${name}": expected "${expectedValue}", got "${actualValue}"`);
113
+ }
114
+ }
115
+ const pass = mismatches.length === 0;
116
+ return {
117
+ pass,
118
+ message: () => pass
119
+ ? `Expected element not to have attributes ${JSON.stringify(expectedAttrs)}`
120
+ : `Expected element attributes to match.\nMismatches: ${mismatches.join(', ')}\nExpected: ${JSON.stringify(expectedAttrs)}\nActual: ${JSON.stringify(actualAttrs)}`,
121
+ };
122
+ }
123
+ /**
124
+ * Check if element has a property
125
+ */
126
+ export function toHaveProperty(received, property, value) {
127
+ const hasProperty = property in received;
128
+ if (!hasProperty) {
129
+ return {
130
+ pass: false,
131
+ message: () => `Expected element to have property "${property}"`,
132
+ };
133
+ }
134
+ if (value !== undefined) {
135
+ const actualValue = received[property];
136
+ const pass = actualValue === value;
137
+ return {
138
+ pass,
139
+ message: () => pass
140
+ ? `Expected element not to have property "${property}" with value ${JSON.stringify(value)}`
141
+ : `Expected element to have property "${property}" with value ${JSON.stringify(value)}, but got ${JSON.stringify(actualValue)}`,
142
+ };
143
+ }
144
+ return {
145
+ pass: true,
146
+ message: () => `Expected element not to have property "${property}"`,
147
+ };
148
+ }
149
+ /**
150
+ * Check if element has text content
151
+ */
152
+ export function toHaveTextContent(received, text) {
153
+ const actualText = received.textContent || '';
154
+ const pass = actualText.includes(text);
155
+ return {
156
+ pass,
157
+ message: () => pass
158
+ ? `Expected element not to have text content "${text}"`
159
+ : `Expected element to have text content "${text}", but got "${actualText}"`,
160
+ };
161
+ }
162
+ /**
163
+ * Check if element's text content exactly matches (after trimming)
164
+ */
165
+ export function toEqualText(received, expectedText) {
166
+ const actualText = (received.textContent || '').trim();
167
+ const trimmedExpected = expectedText.trim();
168
+ const pass = actualText === trimmedExpected;
169
+ return {
170
+ pass,
171
+ message: () => pass
172
+ ? `Expected element text not to equal "${trimmedExpected}"`
173
+ : `Expected element text to equal "${trimmedExpected}", but got "${actualText}"`,
174
+ };
175
+ }
176
+ /**
177
+ * Check if element is visible
178
+ */
179
+ export function toBeVisible(received) {
180
+ const style = window.getComputedStyle(received);
181
+ const pass = style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
182
+ return {
183
+ pass,
184
+ message: () => (pass ? `Expected element not to be visible` : `Expected element to be visible`),
185
+ };
186
+ }
187
+ /**
188
+ * Check if element has shadow root
189
+ */
190
+ export function toHaveShadowRoot(received) {
191
+ const pass = !!received.shadowRoot;
192
+ return {
193
+ pass,
194
+ message: () => (pass ? `Expected element not to have shadow root` : `Expected element to have shadow root`),
195
+ };
196
+ }
197
+ /**
198
+ * Parse HTML string to fragment
199
+ */
200
+ function parseHtmlFragment(html) {
201
+ // Try mock-doc parser first
202
+ try {
203
+ const mockDoc = require('@stencil/core/mock-doc');
204
+ if (mockDoc.parseHtmlToFragment) {
205
+ return mockDoc.parseHtmlToFragment(html);
206
+ }
207
+ }
208
+ catch {
209
+ // Fall through to standard parser
210
+ }
211
+ // Use standard DOM parser (works in jsdom/happy-dom)
212
+ const template = document.createElement('template');
213
+ template.innerHTML = html.trim();
214
+ return template.content;
215
+ }
216
+ /**
217
+ * Custom matcher to check if an element's HTML matches the expected HTML
218
+ * Serializes the entire component tree including shadow DOM
219
+ */
220
+ export function toEqualHtml(received, expected) {
221
+ if (received == null) {
222
+ throw new Error(`expect.toEqualHtml() received value is "${received}"`);
223
+ }
224
+ if (typeof received.then === 'function') {
225
+ throw new TypeError(`Element must be a resolved value, not a promise, before it can be tested`);
226
+ }
227
+ let receivedHtml;
228
+ // Serialize the received value
229
+ if (typeof received === 'string') {
230
+ const fragment = parseHtmlFragment(received);
231
+ // For string inputs, use innerHTML to avoid template wrapper
232
+ receivedHtml = fragment.innerHTML || fragment.textContent || '';
233
+ }
234
+ else if (received.nodeType === 11) {
235
+ // Document fragment
236
+ receivedHtml = serializeHtml(received, { serializeShadowRoot: true });
237
+ }
238
+ else if (received.nodeType === 1) {
239
+ // Element node
240
+ receivedHtml = serializeHtml(received, { serializeShadowRoot: true });
241
+ }
242
+ else {
243
+ throw new TypeError(`expect.toEqualHtml() value should be an element, shadow root, or string`);
244
+ }
245
+ // Parse and serialize expected HTML for consistent formatting
246
+ // For expected HTML, just normalize whitespace without parsing through DOM
247
+ // to preserve custom elements like <mock:shadow-root>
248
+ let expectedHtml = normalizeHtml(expected.trim());
249
+ receivedHtml = normalizeHtml(receivedHtml);
250
+ // Debug logging
251
+ if (expectedHtml !== receivedHtml) {
252
+ console.log('Expected (normalized):', JSON.stringify(expectedHtml));
253
+ console.log('Received (normalized):', JSON.stringify(receivedHtml));
254
+ }
255
+ const pass = receivedHtml === expectedHtml;
256
+ return {
257
+ pass,
258
+ message: () => pass
259
+ ? `Expected HTML not to equal:\n${prettifyHtml(expectedHtml)}`
260
+ : `Expected HTML to equal:\n${prettifyHtml(expectedHtml)}\n\nReceived:\n${prettifyHtml(receivedHtml)}`,
261
+ };
262
+ } /**
263
+ * Custom matcher to check if an element's Light DOM matches the expected HTML
264
+ * Does not serialize shadow DOM
265
+ */
266
+ export function toEqualLightHtml(received, expected) {
267
+ if (received == null) {
268
+ throw new Error(`expect.toEqualLightHtml() received value is "${received}"`);
269
+ }
270
+ if (typeof received.then === 'function') {
271
+ throw new TypeError(`Element must be a resolved value, not a promise, before it can be tested`);
272
+ }
273
+ let receivedHtml;
274
+ // Serialize the received value (without shadow DOM)
275
+ if (typeof received === 'string') {
276
+ const fragment = parseHtmlFragment(received);
277
+ // For string inputs, use innerHTML to avoid template wrapper
278
+ receivedHtml = fragment.innerHTML || fragment.textContent || '';
279
+ }
280
+ else if (received.nodeType === 11) {
281
+ // Document fragment
282
+ receivedHtml = serializeHtml(received, { serializeShadowRoot: false });
283
+ }
284
+ else if (received.nodeType === 1) {
285
+ // Element node
286
+ receivedHtml = serializeHtml(received, { serializeShadowRoot: false });
287
+ }
288
+ else {
289
+ throw new TypeError(`expect.toEqualLightHtml() value should be an element, shadow root, or string`);
290
+ }
291
+ // For expected HTML, just normalize whitespace without parsing through DOM
292
+ // to preserve custom elements like <mock:shadow-root>
293
+ let expectedHtml = normalizeHtml(expected.trim());
294
+ receivedHtml = normalizeHtml(receivedHtml);
295
+ // Debug logging
296
+ if (expectedHtml !== receivedHtml) {
297
+ console.log('LightDOM Expected (normalized):', JSON.stringify(expectedHtml));
298
+ console.log('LightDOM Received (normalized):', JSON.stringify(receivedHtml));
299
+ }
300
+ const pass = receivedHtml === expectedHtml;
301
+ return {
302
+ pass,
303
+ message: () => pass
304
+ ? `Expected Light DOM HTML not to equal:\n${prettifyHtml(expectedHtml)}`
305
+ : `Expected Light DOM HTML to equal:\n${prettifyHtml(expectedHtml)}\n\nReceived:\n${prettifyHtml(receivedHtml)}`,
306
+ };
307
+ }
308
+ /**
309
+ * Check if an EventSpy has received at least one event
310
+ */
311
+ export function toHaveReceivedEvent(received) {
312
+ const pass = received.length > 0;
313
+ return {
314
+ pass,
315
+ message: () => pass
316
+ ? `Expected event "${received.eventName}" not to have been received`
317
+ : `Expected event "${received.eventName}" to have been received, but it was not`,
318
+ };
319
+ }
320
+ /**
321
+ * Check if an EventSpy has received an event a specific number of times
322
+ */
323
+ export function toHaveReceivedEventTimes(received, count) {
324
+ const pass = received.length === count;
325
+ return {
326
+ pass,
327
+ message: () => pass
328
+ ? `Expected event "${received.eventName}" not to have been received ${count} times`
329
+ : `Expected event "${received.eventName}" to have been received ${count} times, but it was received ${received.length} times`,
330
+ };
331
+ }
332
+ /**
333
+ * Safely stringify a value, handling circular references
334
+ */
335
+ function safeStringify(value) {
336
+ try {
337
+ return JSON.stringify(value);
338
+ }
339
+ catch {
340
+ // If circular reference, just return a string representation
341
+ return String(value);
342
+ }
343
+ }
344
+ /**
345
+ * Safely compare two values, handling circular references
346
+ */
347
+ function safeEquals(a, b) {
348
+ // Try JSON comparison first
349
+ try {
350
+ return JSON.stringify(a) === JSON.stringify(b);
351
+ }
352
+ catch {
353
+ // If circular reference, fall back to shallow comparison
354
+ if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {
355
+ const keysA = Object.keys(a);
356
+ const keysB = Object.keys(b);
357
+ if (keysA.length !== keysB.length)
358
+ return false;
359
+ return keysA.every((key) => a[key] === b[key]);
360
+ }
361
+ return a === b;
362
+ }
363
+ }
364
+ /**
365
+ * Check if the last received event has the expected detail
366
+ */
367
+ export function toHaveReceivedEventDetail(received, detail) {
368
+ if (received.length === 0) {
369
+ return {
370
+ pass: false,
371
+ message: () => `Expected event "${received.eventName}" to have been received with detail, but no events were received`,
372
+ };
373
+ }
374
+ const lastEvent = received.lastEvent;
375
+ const pass = safeEquals(lastEvent.detail, detail);
376
+ return {
377
+ pass,
378
+ message: () => pass
379
+ ? `Expected last event detail not to equal ${safeStringify(detail)}`
380
+ : `Expected last event detail to equal ${safeStringify(detail)}, but got ${safeStringify(lastEvent.detail)}`,
381
+ };
382
+ }
383
+ /**
384
+ * Check if the first received event has the expected detail
385
+ */
386
+ export function toHaveFirstReceivedEventDetail(received, detail) {
387
+ if (received.length === 0) {
388
+ return {
389
+ pass: false,
390
+ message: () => `Expected event "${received.eventName}" to have been received with detail, but no events were received`,
391
+ };
392
+ }
393
+ const firstEvent = received.firstEvent;
394
+ const pass = safeEquals(firstEvent.detail, detail);
395
+ return {
396
+ pass,
397
+ message: () => pass
398
+ ? `Expected first event detail not to equal ${safeStringify(detail)}`
399
+ : `Expected first event detail to equal ${safeStringify(detail)}, but got ${safeStringify(firstEvent.detail)}`,
400
+ };
401
+ }
402
+ /**
403
+ * Check if the last received event has the expected detail (alias for toHaveReceivedEventDetail)
404
+ */
405
+ export function toHaveLastReceivedEventDetail(received, detail) {
406
+ return toHaveReceivedEventDetail(received, detail);
407
+ }
408
+ /**
409
+ * Check if the event at a specific index has the expected detail
410
+ */
411
+ export function toHaveNthReceivedEventDetail(received, index, detail) {
412
+ if (received.length === 0) {
413
+ return {
414
+ pass: false,
415
+ message: () => `Expected event "${received.eventName}" to have been received with detail, but no events were received`,
416
+ };
417
+ }
418
+ if (index < 0 || index >= received.length) {
419
+ return {
420
+ pass: false,
421
+ message: () => `Expected event at index ${index}, but only ${received.length} events were received`,
422
+ };
423
+ }
424
+ const event = received.events[index];
425
+ const pass = safeEquals(event.detail, detail);
426
+ return {
427
+ pass,
428
+ message: () => pass
429
+ ? `Expected event at index ${index} detail not to equal ${safeStringify(detail)}`
430
+ : `Expected event at index ${index} detail to equal ${safeStringify(detail)}, but got ${safeStringify(event.detail)}`,
431
+ };
432
+ }
433
+ /**
434
+ * Install custom matchers
435
+ */
436
+ export function installMatchers() {
437
+ expect.extend({
438
+ toHaveClass,
439
+ toHaveClasses,
440
+ toMatchClasses,
441
+ toHaveAttribute,
442
+ toEqualAttribute,
443
+ toEqualAttributes,
444
+ toHaveProperty,
445
+ toHaveTextContent,
446
+ toEqualText,
447
+ toBeVisible,
448
+ toHaveShadowRoot,
449
+ toEqualHtml,
450
+ toEqualLightHtml,
451
+ toHaveReceivedEvent,
452
+ toHaveReceivedEventTimes,
453
+ toHaveReceivedEventDetail,
454
+ toHaveFirstReceivedEventDetail,
455
+ toHaveLastReceivedEventDetail,
456
+ toHaveNthReceivedEventDetail,
457
+ });
458
+ }
459
+ // Auto-install matchers when this module is imported
460
+ installMatchers();
@@ -0,0 +1,11 @@
1
+ import type { RenderResult } from '../types.js';
2
+ interface RenderOptions {
3
+ clearStage?: boolean;
4
+ stageAttrs?: Record<string, string>;
5
+ }
6
+ /**
7
+ * Render using Stencil's render
8
+ */
9
+ export declare function render<T extends HTMLElement = HTMLElement>(vnode: any, options?: RenderOptions): Promise<RenderResult<T>>;
10
+ export {};
11
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +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,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAKD;;GAEG;AACH,wBAAsB,MAAM,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC9D,KAAK,EAAE,GAAG,EACV,OAAO,GAAE,aAGR,GACA,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAyH1B"}