Skip to content

Commit 45dd7cd

Browse files
authored
Merge 42904d1 into 390013e
2 parents 390013e + 42904d1 commit 45dd7cd

File tree

3 files changed

+109
-2
lines changed

3 files changed

+109
-2
lines changed

src/__tests__/utils.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {isInstanceOfElement} from '../utils'
2+
import {setup} from './helpers/utils'
3+
4+
// isInstanceOfElement can be removed once the peerDependency for @testing-library/dom is bumped to a version that includes https://github.com/testing-library/dom-testing-library/pull/885
5+
describe('check element type per isInstanceOfElement', () => {
6+
let defaultViewDescriptor, spanDescriptor
7+
beforeAll(() => {
8+
defaultViewDescriptor = Object.getOwnPropertyDescriptor(
9+
Object.getPrototypeOf(global.document),
10+
'defaultView',
11+
)
12+
spanDescriptor = Object.getOwnPropertyDescriptor(
13+
global.window,
14+
'HTMLSpanElement',
15+
)
16+
})
17+
afterEach(() => {
18+
Object.defineProperty(
19+
Object.getPrototypeOf(global.document),
20+
'defaultView',
21+
defaultViewDescriptor,
22+
)
23+
Object.defineProperty(global.window, 'HTMLSpanElement', spanDescriptor)
24+
})
25+
26+
test('check in regular jest environment', () => {
27+
const {element} = setup(`<span></span>`)
28+
29+
expect(element.ownerDocument.defaultView).toEqual(
30+
expect.objectContaining({
31+
HTMLSpanElement: expect.any(Function),
32+
}),
33+
)
34+
35+
expect(isInstanceOfElement(element, 'HTMLSpanElement')).toBe(true)
36+
expect(isInstanceOfElement(element, 'HTMLDivElement')).toBe(false)
37+
})
38+
39+
test('check in detached document', () => {
40+
const {element} = setup(`<span></span>`)
41+
42+
Object.defineProperty(
43+
Object.getPrototypeOf(element.ownerDocument),
44+
'defaultView',
45+
{value: null},
46+
)
47+
48+
expect(element.ownerDocument.defaultView).toBe(null)
49+
50+
expect(isInstanceOfElement(element, 'HTMLSpanElement')).toBe(true)
51+
expect(isInstanceOfElement(element, 'HTMLDivElement')).toBe(false)
52+
})
53+
54+
test('check in environment not providing constructors on window', () => {
55+
const {element} = setup(`<span></span>`)
56+
57+
delete global.window.HTMLSpanElement
58+
59+
expect(element.ownerDocument.defaultView.HTMLSpanElement).toBe(undefined)
60+
61+
expect(isInstanceOfElement(element, 'HTMLSpanElement')).toBe(true)
62+
expect(isInstanceOfElement(element, 'HTMLDivElement')).toBe(false)
63+
})
64+
65+
test('throw error if element is not created by HTML*Element constructor', () => {
66+
const doc = new Document()
67+
68+
// constructor is global.Element
69+
const element = doc.createElement('span')
70+
71+
expect(() => isInstanceOfElement(element, 'HTMLSpanElement')).toThrow()
72+
})
73+
})

src/select-options.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {createEvent, getConfig, fireEvent} from '@testing-library/dom'
2+
import {isInstanceOfElement} from './utils'
23
import {click} from './click'
34
import {focus} from './focus'
45
import {hover, unhover} from './hover'
@@ -36,7 +37,7 @@ function selectOptionsBase(newValue, select, values, init) {
3637

3738
if (select.disabled || !selectedOptions.length) return
3839

39-
if (select instanceof HTMLSelectElement) {
40+
if (isInstanceOfElement(select, 'HTMLSelectElement')) {
4041
if (select.multiple) {
4142
for (const option of selectedOptions) {
4243
// events fired for multiple select are weird. Can't use hover...

src/utils.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,36 @@
11
import {getConfig} from '@testing-library/dom'
2+
import {getWindowFromNode} from '@testing-library/dom/dist/helpers'
3+
4+
// isInstanceOfElement can be removed once the peerDependency for @testing-library/dom is bumped to a version that includes https://github.com/testing-library/dom-testing-library/pull/885
5+
/**
6+
* Check if an element is of a given type.
7+
*
8+
* @param Element The element to test
9+
* @param string Constructor name. E.g. 'HTMLSelectElement'
10+
*/
11+
function isInstanceOfElement(element, elementType) {
12+
try {
13+
const window = getWindowFromNode(element)
14+
// Window usually has the element constructors as properties but is not required to do so per specs
15+
if (typeof window[elementType] === 'function') {
16+
return element instanceof window[elementType]
17+
}
18+
} catch (e) {
19+
// The document might not be associated with a window
20+
}
21+
22+
// Fall back to the constructor name as workaround for test environments that
23+
// a) not associate the document with a window
24+
// b) not provide the constructor as property of window
25+
if (/^HTML(\w+)Element$/.test(element.constructor.name)) {
26+
return element.constructor.name === elementType
27+
}
28+
29+
// The user passed some node that is not created in a browser-like environment
30+
throw new Error(
31+
`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`,
32+
)
33+
}
234

335
function isMousePressEvent(event) {
436
return (
@@ -256,7 +288,7 @@ const CLICKABLE_INPUT_TYPES = [
256288
function isClickable(element) {
257289
return (
258290
element.tagName === 'BUTTON' ||
259-
(element instanceof element.ownerDocument.defaultView.HTMLInputElement &&
291+
(isInstanceOfElement(element, 'HTMLInputElement') &&
260292
CLICKABLE_INPUT_TYPES.includes(element.type))
261293
)
262294
}
@@ -334,4 +366,5 @@ export {
334366
getValue,
335367
getSelectionRange,
336368
isContentEditable,
369+
isInstanceOfElement,
337370
}

0 commit comments

Comments
 (0)