Opinionated testing package that combines and configures testing libraries to minimize ceremony when writing tests
npx @tessl/cli install tessl/npm-open-wc--testing@4.0.00
# @open-wc/testing
1
2
@open-wc/testing is an opinionated testing package that combines and configures multiple testing libraries to minimize ceremony when writing tests for web components and modern JavaScript applications. It provides Chai assertions, fixture management utilities, and testing helpers in a single, cohesive package. The package offers two import modes: standard (with automatic chai plugin registration) and pure (manual setup required).
3
4
## Package Information
5
6
- **Package Name**: @open-wc/testing
7
- **Package Type**: npm
8
- **Language**: JavaScript/TypeScript
9
- **Installation**: `npm install --save-dev @open-wc/testing`
10
11
## Core Imports
12
13
**Recommended approach (pure imports without automatic plugin registration):**
14
15
```javascript
16
import { expect, fixture, html, chai, assert, should } from '@open-wc/testing/pure';
17
```
18
19
**Standard imports (with automatic chai plugin registration - requires built plugins):**
20
21
```javascript
22
import { expect, fixture, html, chai, assert, should } from '@open-wc/testing';
23
```
24
25
**Note:** The main import may fail at runtime if chai plugins haven't been built. Use the pure import for reliability.
26
27
CommonJS (if using pre-ES6 environment):
28
29
```javascript
30
const { expect, fixture, html, chai, assert, should } = require('@open-wc/testing/pure');
31
```
32
33
## Basic Usage
34
35
```javascript
36
import { expect, fixture, html, elementUpdated } from '@open-wc/testing/pure';
37
38
describe('MyElement', () => {
39
it('renders correctly', async () => {
40
// Create a fixture with lit-html template
41
const el = await fixture(html`<my-element name="test"></my-element>`);
42
43
// Test DOM structure with semantic comparison (requires main import for plugin)
44
// expect(el).dom.to.equal('<my-element name="test"></my-element>');
45
46
// Test accessibility (requires main import for plugin)
47
// await expect(el).to.be.accessible();
48
49
// Basic DOM testing (always works)
50
expect(el.tagName.toLowerCase()).to.equal('my-element');
51
expect(el.getAttribute('name')).to.equal('test');
52
53
// Test element behavior
54
el.name = 'updated';
55
await elementUpdated(el);
56
expect(el.textContent).to.include('updated');
57
});
58
59
it('handles events', async () => {
60
const el = await fixture(html`<button>Click me</button>`);
61
let clicked = false;
62
63
el.addEventListener('click', () => clicked = true);
64
el.click();
65
66
expect(clicked).to.be.true;
67
});
68
});
69
```
70
71
## Architecture
72
73
@open-wc/testing is built around several key components:
74
75
- **Chai Integration**: Pre-configured Chai assertion library with optional automatic plugin registration
76
- **Fixture System**: DOM fixture creation and management for component testing
77
- **Testing Helpers**: Utilities for element lifecycle, events, timing, and browser compatibility
78
- **Plugin System**: Optional semantic DOM diffing and accessibility testing (requires main import)
79
- **Dual Export Modes**:
80
- **Standard** (`@open-wc/testing`): Automatic chai plugin registration, requires built plugins
81
- **Pure** (`@open-wc/testing/pure`): No automatic setup, manual plugin configuration needed
82
83
## Capabilities
84
85
### Chai Assertions
86
87
Core assertion library with pre-configured plugins for comprehensive testing capabilities.
88
89
```typescript { .api }
90
const chai: typeof Chai;
91
const expect: typeof Chai.expect;
92
const assert: typeof Chai.assert;
93
const should: typeof Chai.should;
94
```
95
96
**Usage Examples:**
97
98
```javascript
99
import { expect, assert, should } from '@open-wc/testing/pure';
100
101
// Expect style
102
expect(value).to.equal(42);
103
expect(element).to.have.class('active');
104
105
// Assert style
106
assert.equal(value, 42);
107
assert.isAccessible(element);
108
109
// Should style
110
value.should.equal(42);
111
element.should.have.class('active');
112
```
113
114
### Fixture Management
115
116
Create and manage DOM fixtures for testing components in isolation.
117
118
```typescript { .api }
119
/**
120
* Asynchronously renders template and puts it in DOM, awaiting element updates
121
*/
122
function fixture<T extends Element>(
123
template: LitHTMLRenderable,
124
options?: FixtureOptions
125
): Promise<T>;
126
127
/**
128
* Synchronously renders template and puts it in DOM
129
*/
130
function fixtureSync<T extends Element>(
131
template: LitHTMLRenderable,
132
options?: FixtureOptions
133
): T;
134
135
/**
136
* Specifically handles lit-html templates for async fixture setup
137
*/
138
function litFixture<T extends Element>(
139
template: LitHTMLRenderable,
140
options?: FixtureOptions
141
): Promise<T>;
142
143
/**
144
* Synchronous version of litFixture
145
*/
146
function litFixtureSync<T extends Element>(
147
template: LitHTMLRenderable,
148
options?: FixtureOptions
149
): T;
150
151
/**
152
* Cleans up all defined fixtures by removing wrapper nodes from DOM
153
*/
154
function fixtureCleanup(): void;
155
156
interface FixtureOptions {
157
render?: Function;
158
parentNode?: Element;
159
scopedElements?: ScopedElementsMap;
160
}
161
162
type LitHTMLRenderable =
163
| TemplateResult
164
| TemplateResult[]
165
| Node | Node[]
166
| string | string[]
167
| number | number[]
168
| boolean | boolean[];
169
```
170
171
**Usage Examples:**
172
173
```javascript
174
import { fixture, fixtureSync, fixtureCleanup, html } from '@open-wc/testing/pure';
175
176
// Async fixture creation
177
const el = await fixture(html`<div>Hello World</div>`);
178
const el2 = await fixture('<span>Text content</span>');
179
180
// Sync fixture creation
181
const syncEl = fixtureSync(html`<p>Immediate render</p>`);
182
183
// Custom parent node
184
const customEl = await fixture(html`<div>Child</div>`, {
185
parentNode: document.getElementById('container')
186
});
187
188
// Clean up all fixtures (usually in afterEach)
189
afterEach(() => {
190
fixtureCleanup();
191
});
192
```
193
194
### Template Creation
195
196
Create lit-html templates for fixture rendering and testing.
197
198
```typescript { .api }
199
/**
200
* Creates lit-html templates for testing (tagged template literal)
201
*/
202
const html: (strings: TemplateStringsArray, ...values: any[]) => TemplateResult;
203
204
/**
205
* Allows dynamic tag names in lit-html templates (for testing only)
206
*/
207
function unsafeStatic(value: string): StaticValue;
208
```
209
210
**Usage Examples:**
211
212
```javascript
213
import { fixture, html, unsafeStatic } from '@open-wc/testing/pure';
214
215
// Basic template
216
const el = await fixture(html`<div class="test">Content</div>`);
217
218
// Template with expressions
219
const name = 'Alice';
220
const el2 = await fixture(html`<span>Hello ${name}</span>`);
221
222
// Dynamic tag names (use carefully)
223
const tagName = unsafeStatic('my-element');
224
const el3 = await fixture(html`<${tagName} prop="value"></${tagName}>`);
225
```
226
227
### Element Lifecycle Management
228
229
Manage element updates, custom element registration, and lifecycle events.
230
231
```typescript { .api }
232
/**
233
* Awaits element update completion (supports Lit, Stencil, generic elements)
234
*/
235
function elementUpdated<T extends Element>(el: T): Promise<T>;
236
237
/**
238
* Registers new custom element with auto-generated unique name
239
*/
240
function defineCE<T extends HTMLElement>(klass: Constructor<T>): string;
241
242
type Constructor<T = {}> = new (...args: any[]) => T;
243
```
244
245
**Usage Examples:**
246
247
```javascript
248
import { fixture, elementUpdated, defineCE, html } from '@open-wc/testing/pure';
249
250
// Wait for element updates
251
const el = await fixture(html`<my-element></my-element>`);
252
el.someProperty = 'new value';
253
await elementUpdated(el); // Waits for Lit/Stencil updates
254
255
// Register custom element for testing
256
class TestElement extends HTMLElement {
257
connectedCallback() {
258
this.textContent = 'Test Element';
259
}
260
}
261
const tagName = defineCE(TestElement); // Returns 'test-0', 'test-1', etc.
262
const el2 = await fixture(`<${tagName}></${tagName}>`);
263
```
264
265
### Event Handling
266
267
Utilities for testing event-driven behavior and user interactions.
268
269
```typescript { .api }
270
/**
271
* Listens for one event and resolves with the event object
272
*/
273
function oneEvent(
274
eventTarget: EventTarget,
275
eventName: string
276
): Promise<Event>;
277
278
/**
279
* Like oneEvent but automatically calls preventDefault() on the event
280
*/
281
function oneDefaultPreventedEvent(
282
eventTarget: EventTarget,
283
eventName: string
284
): Promise<Event>;
285
286
/**
287
* Focuses element with IE11 compatibility workarounds
288
*/
289
function triggerFocusFor(element: HTMLElement): Promise<void>;
290
291
/**
292
* Blurs element with IE11 compatibility workarounds
293
*/
294
function triggerBlurFor(element: HTMLElement): Promise<void>;
295
```
296
297
**Usage Examples:**
298
299
```javascript
300
import { fixture, oneEvent, oneDefaultPreventedEvent, triggerFocusFor } from '@open-wc/testing/pure';
301
302
// Listen for custom events
303
const el = await fixture(html`<my-button></my-button>`);
304
const eventPromise = oneEvent(el, 'my-custom-event');
305
el.dispatchEvent(new CustomEvent('my-custom-event', { detail: 'data' }));
306
const event = await eventPromise;
307
console.log(event.detail); // 'data'
308
309
// Prevent default behavior
310
const link = await fixture(html`<a href="/test">Link</a>`);
311
const clickPromise = oneDefaultPreventedEvent(link, 'click');
312
link.click();
313
await clickPromise; // Event was prevented
314
315
// Focus management
316
const input = await fixture(html`<input type="text">`);
317
await triggerFocusFor(input);
318
expect(document.activeElement).to.equal(input);
319
```
320
321
### Timing and Async Utilities
322
323
Control timing and wait for conditions during tests.
324
325
```typescript { .api }
326
/**
327
* Resolves after specified milliseconds using setTimeout
328
*/
329
function aTimeout(ms: number): Promise<void>;
330
331
/**
332
* Resolves after requestAnimationFrame
333
*/
334
function nextFrame(): Promise<void>;
335
336
/**
337
* Waits until predicate returns truthy value, polling at intervals
338
*/
339
function waitUntil(
340
predicate: () => unknown | Promise<unknown>,
341
message?: string,
342
options?: { interval?: number; timeout?: number }
343
): Promise<void>;
344
```
345
346
**Usage Examples:**
347
348
```javascript
349
import { aTimeout, nextFrame, waitUntil } from '@open-wc/testing/pure';
350
351
// Simple timeout
352
await aTimeout(100); // Wait 100ms
353
354
// Wait for animation frame
355
await nextFrame(); // Wait for next render cycle
356
357
// Wait for condition with polling
358
const el = await fixture(html`<div></div>`);
359
setTimeout(() => el.classList.add('loaded'), 50);
360
361
await waitUntil(
362
() => el.classList.contains('loaded'),
363
'Element should be loaded',
364
{ interval: 10, timeout: 1000 }
365
);
366
```
367
368
### Browser Compatibility
369
370
Utilities for handling browser-specific behavior and compatibility.
371
372
```typescript { .api }
373
/**
374
* Detects Internet Explorer browser
375
*/
376
function isIE(): boolean;
377
```
378
379
**Usage Examples:**
380
381
```javascript
382
import { isIE } from '@open-wc/testing/pure';
383
384
if (isIE()) {
385
// Skip test or use IE-specific behavior
386
console.log('Running in Internet Explorer');
387
}
388
```
389
390
### Semantic DOM Comparison
391
392
Advanced DOM comparison that ignores whitespace, comments, and other non-semantic differences.
393
394
**New Chai Assertions:**
395
396
```typescript { .api }
397
// Available on any DOM element via expect()
398
interface Assertion {
399
dom: Assertion; // Compare full DOM trees semantically
400
lightDom: Assertion; // Compare light DOM only
401
}
402
403
// Additional methods on dom/lightDom assertions
404
interface Assertion {
405
equal(expected: string): Assertion; // Semantic equality
406
equalSnapshot(name?: string): Assertion; // Snapshot testing
407
}
408
```
409
410
**Usage Examples:**
411
412
```javascript
413
import { expect, fixture } from '@open-wc/testing/pure';
414
415
// Semantic DOM comparison ignores formatting
416
const el = await fixture(`<div><!-- comment --><h1> Hello </h1> </div>`);
417
expect(el).dom.to.equal('<div><h1>Hello</h1></div>');
418
419
// Light DOM comparison (excludes shadow DOM)
420
expect(el).lightDom.to.equal('<h1>Hello</h1>');
421
422
// Snapshot testing (when supported by test runner)
423
expect(el).dom.to.equalSnapshot();
424
expect(el).dom.to.equalSnapshot('custom-name');
425
```
426
427
### Accessibility Testing
428
429
Automated accessibility testing using axe-core integration.
430
431
**New Chai Assertions:**
432
433
```typescript { .api }
434
interface Assertion {
435
accessible(options?: A11yOptions): Promise<Assertion>;
436
}
437
438
interface A11yOptions {
439
ignoredRules?: string[]; // Skip specific axe rules
440
done?: Function; // Callback for async testing patterns
441
}
442
443
// Also available on assert interface
444
declare namespace assert {
445
function isAccessible(
446
element: Element,
447
options?: A11yOptions
448
): Promise<void>;
449
}
450
```
451
452
**Usage Examples:**
453
454
```javascript
455
import { expect, assert, fixture, html } from '@open-wc/testing/pure';
456
457
// Basic accessibility testing
458
const button = await fixture(html`<button>Click me</button>`);
459
await expect(button).to.be.accessible();
460
461
// Ignore specific rules
462
const problematicEl = await fixture(html`<div aria-labelledby="missing-id"></div>`);
463
await expect(problematicEl).to.be.accessible({
464
ignoredRules: ['aria-allowed-attr']
465
});
466
467
// Assert style
468
const form = await fixture(html`<form><label>Name <input></label></form>`);
469
await assert.isAccessible(form);
470
471
// Negation for expected failures
472
const badEl = await fixture(html`<div aria-labelledby="missing"></div>`);
473
await expect(badEl).not.to.be.accessible();
474
```
475
476
### Built-in Chai Plugins Status
477
478
**Available Plugins (functional with main import):**
479
- `@open-wc/semantic-dom-diff` - Provides `.dom.to.equal()` and `.lightDom.to.equal()` assertions
480
- `chai-a11y-axe` - Provides `.accessible()` accessibility testing assertions
481
482
**Currently Non-functional Plugins:**
483
The package references `chai-dom` and `sinon-chai` plugins but they require a build step that may not be available. If these plugins are working in your environment, they would provide:
484
485
- `chai-dom` - DOM assertions like `.to.have.class()`, `.to.have.attribute()`
486
- `sinon-chai` - Spy/stub assertions like `.to.have.callCount()`
487
488
**Recommendation:** Use the pure import (`@open-wc/testing/pure`) and manually configure additional chai plugins as needed to avoid runtime import errors.
489
490
## Types
491
492
```typescript { .api }
493
// Re-exported from @esm-bundle/chai
494
type Chai = typeof import('chai');
495
496
// Fixture configuration
497
interface FixtureOptions {
498
render?: Function;
499
parentNode?: Element;
500
scopedElements?: ScopedElementsMap;
501
}
502
503
// Template types for fixture creation
504
type LitHTMLRenderable =
505
| TemplateResult
506
| TemplateResult[]
507
| Node | Node[]
508
| string | string[]
509
| number | number[]
510
| boolean | boolean[];
511
512
// Event handling types
513
type OneEventFn = <TEvent extends Event = CustomEvent>(
514
eventTarget: EventTarget,
515
eventName: string
516
) => Promise<TEvent>;
517
518
// Element constructor type
519
type Constructor<T = {}> = new (...args: any[]) => T;
520
521
// Accessibility testing options
522
interface A11yOptions {
523
ignoredRules?: string[];
524
done?: Function;
525
}
526
527
// Timing utility options
528
interface WaitUntilOptions {
529
interval?: number; // Polling interval in ms (default: 50)
530
timeout?: number; // Total timeout in ms (default: 1000)
531
}
532
```
533
534
## Usage Recommendations
535
536
**For maximum reliability and compatibility:**
537
538
1. **Use the pure import**: `@open-wc/testing/pure` to avoid potential runtime issues
539
2. **Manual plugin setup**: If you need DOM or accessibility assertions, configure chai plugins manually
540
3. **Standard testing**: The package works perfectly for fixture creation, element testing, and basic chai assertions
541
4. **Version compatibility**: This documentation reflects @open-wc/testing@4.0.0 behavior
542
543
**Example minimal setup:**
544
545
```javascript
546
import { expect, fixture, html, elementUpdated } from '@open-wc/testing/pure';
547
548
describe('Component Tests', () => {
549
it('creates fixtures and tests elements', async () => {
550
const el = await fixture(html`<div class="test">Hello</div>`);
551
expect(el.textContent).to.equal('Hello');
552
expect(el.className).to.equal('test');
553
});
554
});
555
```