Utilities to drill down and manipulate back to the DOM
Last active
July 15, 2021 01:14
-
-
Save renoirb/3cb1622d7304efc713e3a8ff28b828d3 to your computer and use it in GitHub Desktop.
DOM Coercion helpers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { coerceGlobalWindow, coerceOwnerDocument, assertsIsDocument } from './dom-helpers' | |
describe('dom-helper.ts/coerce', () => { | |
// https://www.npmjs.com/package/@types/jsdom | |
const jsdom = jest.requireActual('jsdom') | |
let doc: Document | |
beforeEach(() => { | |
const { JSDOM } = jsdom | |
const vm = new JSDOM( | |
`<html data-example="hello"><head /><body><h1>Hi</h1><div><div id="app">Nested</div></div></body></html>`, | |
{ | |
pretendToBeVisual: true, | |
beforeParse(w: Window) { | |
w.document.addEventListener('error', (...args) => { | |
console.log('beforeParse error', args) | |
throw new Error('error') | |
}) | |
}, | |
}, | |
) | |
coerceOwnerDocument(vm.window.document.body) | |
doc = vm.window.document as Document | |
}) | |
describe('coerceOwnerDocument', () => { | |
it('should not throw when is a valid document', () => { | |
expect(() => coerceOwnerDocument(doc)).not.toThrow() | |
expect(() => coerceOwnerDocument(document /* Jest's internal JSDOM instance */)).not.toThrow() | |
}) | |
it('should also work with a window object', () => { | |
expect(() => coerceOwnerDocument(window /* Jest's internal JSDOM instance */)).not.toThrow() | |
}) | |
it('should not throw when we are using current getDocument helper', () => { | |
expect(() => coerceOwnerDocument(document)).not.toThrow() | |
}) | |
it('can get the ownerDocument from a child node', () => { | |
expect(() => coerceOwnerDocument(doc.getElementById('app'))).not.toThrow() | |
expect(coerceOwnerDocument(doc.getElementById('app')).documentElement).toHaveAttribute('data-example', 'hello') | |
}) | |
}) | |
describe('assertsIsDocument', () => { | |
it('should not throw when is a valid document', () => { | |
expect(() => assertsIsDocument(doc)).not.toThrow() | |
expect(() => assertsIsDocument(document /* Jest's internal JSDOM instance */)).not.toThrow() | |
}) | |
}) | |
describe('coerceGlobalWindow', () => { | |
it('should not throw when is a valid document', () => { | |
expect(() => coerceGlobalWindow(doc)).not.toThrow() | |
expect(() => coerceGlobalWindow(document /* Jest's internal JSDOM instance */)).not.toThrow() | |
expect(coerceGlobalWindow(doc)).toHaveProperty('name') | |
}) | |
}) | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Ensure we have a valid Document object before accessing it. | |
* | |
* @param node - any object that might be a valid Document | |
*/ | |
export const assertsIsDocument: (node: unknown) => asserts node is Document = node => { | |
let mustBeTrue = false | |
let message = 'We could not confirm we received a Document object' | |
try { | |
coerceOwnerDocument(node ?? null) | |
// Above must throw if is not a Document node | |
mustBeTrue = true | |
} catch (e) { | |
message += e | |
} | |
if (!mustBeTrue) { | |
throw new TypeError(message) | |
} | |
// Since it's a TypeScript user defined assertion function it either throws or return void | |
} | |
export const coerceOwnerDocument: (node: unknown) => Document = node => { | |
let message = '' | |
let d: Document | undefined | |
if (node && typeof node === 'object') { | |
if ('document' in node) { | |
const w = node as { document?: Document } | |
if (w.document) { | |
d = w.document as Document | |
} | |
} | |
if ('nodeType' in node && 'ownerDocument' in node) { | |
const unkownElement: { ownerDocument?: Document | null } = node | |
const { ownerDocument = null } = unkownElement | |
if (ownerDocument !== null) { | |
d = unkownElement.ownerDocument ?? void 0 | |
} else if (ownerDocument === null) { | |
d = node as Document | |
} | |
} | |
if (d && typeof d === 'object' && 'defaultView' in d && 'body' in d && 'nodeType' in d) { | |
const { nodeType = 0, body } = d as Document | |
if (nodeType === 9 && body.nodeType === 1) { | |
return d | |
} | |
} | |
} | |
message += 'We did not receive a valid DOM node' | |
throw new TypeError(message) | |
} | |
export const coerceGlobalWindow: (node: unknown) => Window = node => { | |
const d = coerceOwnerDocument(node) | |
// Above must throw | |
if ('defaultView' in d && d.defaultView) { | |
return d.defaultView | |
} | |
const message = 'We could not get to this context’s root Window' | |
throw new TypeError(message) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment