Skip to content

Instantly share code, notes, and snippets.

@renoirb
Last active July 15, 2021 01:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save renoirb/3cb1622d7304efc713e3a8ff28b828d3 to your computer and use it in GitHub Desktop.
Save renoirb/3cb1622d7304efc713e3a8ff28b828d3 to your computer and use it in GitHub Desktop.
DOM Coercion helpers

DOM Cœrcion utils

Utilities to drill down and manipulate back to the DOM

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')
})
})
})
/**
* 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