Skip to content

Commit

Permalink
fix: add isInstanceOfElement helper (#552)
Browse files Browse the repository at this point in the history
  • Loading branch information
ph-fritsche committed Feb 1, 2021
1 parent 390013e commit b4330c4
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 2 deletions.
73 changes: 73 additions & 0 deletions src/__tests__/utils.js
@@ -0,0 +1,73 @@
import {isInstanceOfElement} from '../utils'
import {setup} from './helpers/utils'

// 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
describe('check element type per isInstanceOfElement', () => {
let defaultViewDescriptor, spanDescriptor
beforeAll(() => {
defaultViewDescriptor = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(global.document),
'defaultView',
)
spanDescriptor = Object.getOwnPropertyDescriptor(
global.window,
'HTMLSpanElement',
)
})
afterEach(() => {
Object.defineProperty(
Object.getPrototypeOf(global.document),
'defaultView',
defaultViewDescriptor,
)
Object.defineProperty(global.window, 'HTMLSpanElement', spanDescriptor)
})

test('check in regular jest environment', () => {
const {element} = setup(`<span></span>`)

expect(element.ownerDocument.defaultView).toEqual(
expect.objectContaining({
HTMLSpanElement: expect.any(Function),
}),
)

expect(isInstanceOfElement(element, 'HTMLSpanElement')).toBe(true)
expect(isInstanceOfElement(element, 'HTMLDivElement')).toBe(false)
})

test('check in detached document', () => {
const {element} = setup(`<span></span>`)

Object.defineProperty(
Object.getPrototypeOf(element.ownerDocument),
'defaultView',
{value: null},
)

expect(element.ownerDocument.defaultView).toBe(null)

expect(isInstanceOfElement(element, 'HTMLSpanElement')).toBe(true)
expect(isInstanceOfElement(element, 'HTMLDivElement')).toBe(false)
})

test('check in environment not providing constructors on window', () => {
const {element} = setup(`<span></span>`)

delete global.window.HTMLSpanElement

expect(element.ownerDocument.defaultView.HTMLSpanElement).toBe(undefined)

expect(isInstanceOfElement(element, 'HTMLSpanElement')).toBe(true)
expect(isInstanceOfElement(element, 'HTMLDivElement')).toBe(false)
})

test('throw error if element is not created by HTML*Element constructor', () => {
const doc = new Document()

// constructor is global.Element
const element = doc.createElement('span')

expect(() => isInstanceOfElement(element, 'HTMLSpanElement')).toThrow()
})
})
3 changes: 2 additions & 1 deletion src/select-options.js
@@ -1,4 +1,5 @@
import {createEvent, getConfig, fireEvent} from '@testing-library/dom'
import {isInstanceOfElement} from './utils'
import {click} from './click'
import {focus} from './focus'
import {hover, unhover} from './hover'
Expand Down Expand Up @@ -36,7 +37,7 @@ function selectOptionsBase(newValue, select, values, init) {

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

if (select instanceof HTMLSelectElement) {
if (isInstanceOfElement(select, 'HTMLSelectElement')) {
if (select.multiple) {
for (const option of selectedOptions) {
// events fired for multiple select are weird. Can't use hover...
Expand Down
35 changes: 34 additions & 1 deletion src/utils.js
@@ -1,4 +1,36 @@
import {getConfig} from '@testing-library/dom'
import {getWindowFromNode} from '@testing-library/dom/dist/helpers'

// 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
/**
* Check if an element is of a given type.
*
* @param Element The element to test
* @param string Constructor name. E.g. 'HTMLSelectElement'
*/
function isInstanceOfElement(element, elementType) {
try {
const window = getWindowFromNode(element)
// Window usually has the element constructors as properties but is not required to do so per specs
if (typeof window[elementType] === 'function') {
return element instanceof window[elementType]
}
} catch (e) {
// The document might not be associated with a window
}

// Fall back to the constructor name as workaround for test environments that
// a) not associate the document with a window
// b) not provide the constructor as property of window
if (/^HTML(\w+)Element$/.test(element.constructor.name)) {
return element.constructor.name === elementType
}

// The user passed some node that is not created in a browser-like environment
throw new Error(
`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`,
)
}

function isMousePressEvent(event) {
return (
Expand Down Expand Up @@ -256,7 +288,7 @@ const CLICKABLE_INPUT_TYPES = [
function isClickable(element) {
return (
element.tagName === 'BUTTON' ||
(element instanceof element.ownerDocument.defaultView.HTMLInputElement &&
(isInstanceOfElement(element, 'HTMLInputElement') &&
CLICKABLE_INPUT_TYPES.includes(element.type))
)
}
Expand Down Expand Up @@ -334,4 +366,5 @@ export {
getValue,
getSelectionRange,
isContentEditable,
isInstanceOfElement,
}

0 comments on commit b4330c4

Please sign in to comment.