From 37907da12517ac40ef9436cc377939dc6d0a1e12 Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Sun, 31 Jan 2021 15:55:12 +0100 Subject: [PATCH] feat: add isInstanceOfElement helper --- src/__tests__/helpers.js | 86 ++++++++++++++++++++++++++++++++++++++++ src/helpers.js | 31 +++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/src/__tests__/helpers.js b/src/__tests__/helpers.js index 1bc858374..630e9a57d 100644 --- a/src/__tests__/helpers.js +++ b/src/__tests__/helpers.js @@ -3,7 +3,9 @@ import { getWindowFromNode, checkContainerType, runWithRealTimers, + isInstanceOfElement, } from '../helpers' +import {render} from './helpers/test-utils' const globalObj = typeof window === 'undefined' ? global : window @@ -53,6 +55,90 @@ describe('query container validation throws when validation fails', () => { }) }) +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 {container} = render(``) + + expect(container.firstChild.ownerDocument.defaultView).toEqual( + expect.objectContaining({ + HTMLSpanElement: expect.any(Function), + }), + ) + + expect(isInstanceOfElement(container.firstChild, 'HTMLSpanElement')).toBe( + true, + ) + expect(isInstanceOfElement(container.firstChild, 'HTMLDivElement')).toBe( + false, + ) + }) + + test('check in detached document', () => { + const {container} = render(``) + + Object.defineProperty( + Object.getPrototypeOf(container.ownerDocument), + 'defaultView', + {value: null}, + ) + + expect(container.firstChild.ownerDocument.defaultView).toBe(null) + + expect(isInstanceOfElement(container.firstChild, 'HTMLSpanElement')).toBe( + true, + ) + expect(isInstanceOfElement(container.firstChild, 'HTMLDivElement')).toBe( + false, + ) + }) + + test('check in environment not providing constructors on window', () => { + const {container} = render(``) + + delete global.window.HTMLSpanElement + + expect(container.firstChild.ownerDocument.defaultView.HTMLSpanElement).toBe( + undefined, + ) + + expect(isInstanceOfElement(container.firstChild, 'HTMLSpanElement')).toBe( + true, + ) + expect(isInstanceOfElement(container.firstChild, '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() + }) +}) + test('should always use realTimers before using callback when timers are faked with useFakeTimers', () => { const originalSetTimeout = globalObj.setTimeout diff --git a/src/helpers.js b/src/helpers.js index f686d46ba..8245fd819 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -100,6 +100,36 @@ function getWindowFromNode(node) { } } +/** + * 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 checkContainerType(container) { if ( !container || @@ -131,4 +161,5 @@ export { checkContainerType, jestFakeTimersAreEnabled, TEXT_NODE, + isInstanceOfElement, }