Skip to content

Commit 37907da

Browse files
committed
feat: add isInstanceOfElement helper
1 parent 5bc9364 commit 37907da

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

src/__tests__/helpers.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import {
33
getWindowFromNode,
44
checkContainerType,
55
runWithRealTimers,
6+
isInstanceOfElement,
67
} from '../helpers'
8+
import {render} from './helpers/test-utils'
79

810
const globalObj = typeof window === 'undefined' ? global : window
911

@@ -53,6 +55,90 @@ describe('query container validation throws when validation fails', () => {
5355
})
5456
})
5557

58+
describe('check element type per isInstanceOfElement', () => {
59+
let defaultViewDescriptor, spanDescriptor
60+
beforeAll(() => {
61+
defaultViewDescriptor = Object.getOwnPropertyDescriptor(
62+
Object.getPrototypeOf(global.document),
63+
'defaultView',
64+
)
65+
spanDescriptor = Object.getOwnPropertyDescriptor(
66+
global.window,
67+
'HTMLSpanElement',
68+
)
69+
})
70+
afterEach(() => {
71+
Object.defineProperty(
72+
Object.getPrototypeOf(global.document),
73+
'defaultView',
74+
defaultViewDescriptor,
75+
)
76+
Object.defineProperty(global.window, 'HTMLSpanElement', spanDescriptor)
77+
})
78+
79+
test('check in regular jest environment', () => {
80+
const {container} = render(`<span></span>`)
81+
82+
expect(container.firstChild.ownerDocument.defaultView).toEqual(
83+
expect.objectContaining({
84+
HTMLSpanElement: expect.any(Function),
85+
}),
86+
)
87+
88+
expect(isInstanceOfElement(container.firstChild, 'HTMLSpanElement')).toBe(
89+
true,
90+
)
91+
expect(isInstanceOfElement(container.firstChild, 'HTMLDivElement')).toBe(
92+
false,
93+
)
94+
})
95+
96+
test('check in detached document', () => {
97+
const {container} = render(`<span></span>`)
98+
99+
Object.defineProperty(
100+
Object.getPrototypeOf(container.ownerDocument),
101+
'defaultView',
102+
{value: null},
103+
)
104+
105+
expect(container.firstChild.ownerDocument.defaultView).toBe(null)
106+
107+
expect(isInstanceOfElement(container.firstChild, 'HTMLSpanElement')).toBe(
108+
true,
109+
)
110+
expect(isInstanceOfElement(container.firstChild, 'HTMLDivElement')).toBe(
111+
false,
112+
)
113+
})
114+
115+
test('check in environment not providing constructors on window', () => {
116+
const {container} = render(`<span></span>`)
117+
118+
delete global.window.HTMLSpanElement
119+
120+
expect(container.firstChild.ownerDocument.defaultView.HTMLSpanElement).toBe(
121+
undefined,
122+
)
123+
124+
expect(isInstanceOfElement(container.firstChild, 'HTMLSpanElement')).toBe(
125+
true,
126+
)
127+
expect(isInstanceOfElement(container.firstChild, 'HTMLDivElement')).toBe(
128+
false,
129+
)
130+
})
131+
132+
test('throw error if element is not created by HTML*Element constructor', () => {
133+
const doc = new Document()
134+
135+
// constructor is global.Element
136+
const element = doc.createElement('span')
137+
138+
expect(() => isInstanceOfElement(element, 'HTMLSpanElement')).toThrow()
139+
})
140+
})
141+
56142
test('should always use realTimers before using callback when timers are faked with useFakeTimers', () => {
57143
const originalSetTimeout = globalObj.setTimeout
58144

src/helpers.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,36 @@ function getWindowFromNode(node) {
100100
}
101101
}
102102

103+
/**
104+
* Check if an element is of a given type.
105+
*
106+
* @param Element The element to test
107+
* @param string Constructor name. E.g. 'HTMLSelectElement'
108+
*/
109+
function isInstanceOfElement(element, elementType) {
110+
try {
111+
const window = getWindowFromNode(element)
112+
// Window usually has the element constructors as properties but is not required to do so per specs
113+
if (typeof window[elementType] === 'function') {
114+
return element instanceof window[elementType]
115+
}
116+
} catch (e) {
117+
// The document might not be associated with a window
118+
}
119+
120+
// Fall back to the constructor name as workaround for test environments that
121+
// a) not associate the document with a window
122+
// b) not provide the constructor as property of window
123+
if (/^HTML(\w+)Element$/.test(element.constructor.name)) {
124+
return element.constructor.name === elementType
125+
}
126+
127+
// The user passed some node that is not created in a browser-like environment
128+
throw new Error(
129+
`Unable to verify if element is instance of ${elementType}. Please file an issue describing your test environment: https://github.com/testing-library/dom-testing-library/issues/new`,
130+
)
131+
}
132+
103133
function checkContainerType(container) {
104134
if (
105135
!container ||
@@ -131,4 +161,5 @@ export {
131161
checkContainerType,
132162
jestFakeTimersAreEnabled,
133163
TEXT_NODE,
164+
isInstanceOfElement,
134165
}

0 commit comments

Comments
 (0)