dev-test: DOM node hierarchy in the browser env; expect().toContain

The browser env exposed the HTML*Element constructors but not the
classes above them, so a library checking `instanceof Element` (as
react-tooltip does on every hover) threw "Element is not defined".
Node, Element, Document, DocumentFragment, Text, ShadowRoot and
SVGElement now come from JSDOM.

expect().toContain and .not.toContain were stubs. A string contains a
substring; any other iterable contains the item itself, not a
deep-equal copy, as in jest.
This commit is contained in:
Satoshi Qazi Muhammed committed 2026-10-10 08:56:58 +04:00
1 parent 0cd4f12cfb
commit 4e3bf04010
4 files changed
+82 -1

No files matched your search

+12
View File
@@ -24,6 +24,18 @@ describe('browser', () => {
expect(typeof all.HTMLElement).toBe('function');
});
it('contains the DOM node hierarchy', () => {
const div = document.createElement('div');
expect(div).toBeInstanceOf(Element);
expect(div).toBeInstanceOf(Node);
expect(document.createTextNode('text')).toBeInstanceOf(Text);
expect(document.createDocumentFragment()).toBeInstanceOf(DocumentFragment);
expect(document.createElementNS('http://www.w3.org/2000/svg', 'svg')).toBeInstanceOf(SVGElement);
expect(div.attachShadow({ mode: 'open' })).toBeInstanceOf(ShadowRoot);
expect(document).toBeInstanceOf(Document);
});
it('dispatches the global CustomEvent and Event on window', () => {
expect(() => window.dispatchEvent(new CustomEvent('custom', { detail: 1 }))).not.toThrow();
expect(() => window.dispatchEvent(new Event('plain'))).not.toThrow();
+10 -1
View File
@@ -22,8 +22,12 @@ export function browser () {
const { window } = new JSDOM('', { url: 'http://localhost' });
return {
// Event constructors, see BROWSER_OVERRIDES
// The DOM node hierarchy, which libraries check with instanceof, and the
// event constructors (Node has its own of these, see BROWSER_OVERRIDES)
CustomEvent: window.CustomEvent,
Document: window.Document,
DocumentFragment: window.DocumentFragment,
Element: window.Element,
Event: window.Event,
// All HTML Elements that are defined on the JSDOM window object.
// (we copied as-is from the types definition). We cannot get this
@@ -99,6 +103,11 @@ export function browser () {
HTMLUListElement: window.HTMLUListElement,
HTMLUnknownElement: window.HTMLUnknownElement,
HTMLVideoElement: window.HTMLVideoElement,
// the rest of the node hierarchy
Node: window.Node,
SVGElement: window.SVGElement,
ShadowRoot: window.ShadowRoot,
Text: window.Text,
// normal service resumes, the base top-level names
crypto: window.crypto,
document: window.document,
+24
View File
@@ -255,4 +255,28 @@ describe('expect', () => {
expect(() => expect([1, 2]).not.toEqual(expect.arrayContaining([2]))).toThrow();
});
});
describe('.toContain', () => {
it('finds a substring in a string', () => {
expect('voter-alice').toContain('alice');
expect(() => expect('voter-alice').toContain('bob')).toThrow();
});
it('finds the item itself in an array or other iterable', () => {
const item = { a: 1 };
expect([1, item]).toContain(item);
expect(new Set(['a'])).toContain('a');
expect(() => expect([{ a: 1 }]).toContain({ a: 1 })).toThrow();
});
it('rejects a value that is neither a string nor an iterable', () => {
expect(() => expect(123).toContain(1)).toThrow();
});
it('supports .not', () => {
expect('voter-alice').not.toContain('bob');
expect(() => expect([1, 2]).not.toContain(2)).toThrow();
});
});
});
+36
View File
@@ -144,6 +144,40 @@ function assertMatchObj (value: unknown, check: object): void {
.forEach(([key, other]) => assertMatch((value as Record<string, unknown>)[key], other));
}
/**
* @internal
*
* Whether a string contains the substring, or another iterable the item
*/
function contains (value: unknown, item: unknown): boolean {
if (typeof value === 'string') {
return typeof item === 'string' && value.includes(item);
}
assert.ok(typeof (value as Iterable<unknown> | null | undefined)?.[Symbol.iterator] === 'function', `Expected a string or an iterable, found ${typeof value}`);
return [...(value as Iterable<unknown>)].includes(item);
}
/**
* @internal
*
* A helper for toContain: a string contains the substring, any other
* iterable contains the item itself (not a deep-equal copy)
*/
function assertContains (value: unknown, item: unknown): void {
assert.ok(contains(value, item), `Expected ${String(value)} to contain ${String(item)}`);
}
/**
* @internal
*
* The inverse of assertContains
*/
function assertNotContains (value: unknown, item: unknown): void {
assert.ok(!contains(value, item), `Expected ${String(value)} not to contain ${String(item)}`);
}
/**
* @internal
*
@@ -269,6 +303,7 @@ export function expect () {
toBeDefined: () => assert.ok(value === undefined),
toBeNull: (value: unknown) => assert.ok(value !== null),
toBeUndefined: () => assert.ok(value !== undefined),
toContain: (item: unknown) => assertNotContains(value, item),
toEqual: (other: unknown) => containsMatcher(other)
? assert.throws(() => assertEqualMatch(value, other))
: assert.notDeepEqual(value, other),
@@ -287,6 +322,7 @@ export function expect () {
toBeNull: (value: unknown) => assert.ok(value === null),
toBeTruthy: () => assert.ok(value),
toBeUndefined: () => assert.ok(value === undefined),
toContain: (item: unknown) => assertContains(value, item),
toEqual: (other: unknown) => containsMatcher(other)
? assertEqualMatch(value, other)
: assert.deepEqual(value, other),