Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(logDOM): add logDOM export #336

Merged
merged 1 commit into from
Aug 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 50 additions & 16 deletions src/__tests__/pretty-dom.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,61 @@
import {prettyDOM} from '../pretty-dom'
import {render} from './helpers/test-utils'
import {prettyDOM, logDOM} from '../pretty-dom'
import {render, renderIntoDocument} from './helpers/test-utils'

test('prints out the given DOM element tree', () => {
beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => {})
})

afterEach(() => {
console.log.mockRestore()
})

test('prettyDOM prints out the given DOM element tree', () => {
const {container} = render('<div>Hello World!</div>')
expect(prettyDOM(container)).toMatchInlineSnapshot(`
"<div>
<div>
Hello World!
</div>
</div>"
`)
"<div>
<div>
Hello World!
</div>
</div>"
`)
})

test('supports truncating the output length', () => {
test('prettyDOM supports truncating the output length', () => {
const {container} = render('<div>Hello World!</div>')
expect(prettyDOM(container, 5)).toMatch(/\.\.\./)
})

test('supports receiving the document element', () => {
test('prettyDOM defaults to document.body', () => {
renderIntoDocument('<div>Hello World!</div>')
expect(prettyDOM()).toMatchInlineSnapshot(`
"<body>
<div>
Hello World!
</div>
</body>"
`)
})

test('prettyDOM supports receiving the document element', () => {
expect(prettyDOM(document)).toMatchInlineSnapshot(`
"<html>
<head />
<body />
</html>"
`)
"<html>
<head />
<body />
</html>"
`)
})

test('logDOM logs prettyDOM to the console', () => {
const {container} = render('<div>Hello World!</div>')
logDOM(container)
expect(console.log).toHaveBeenCalledTimes(1)
expect(console.log.mock.calls[0][0]).toMatchInlineSnapshot(`
"<div>
<div>
Hello World!
</div>
</div>"
`)
})

/* eslint no-console:0 */
17 changes: 10 additions & 7 deletions src/__tests__/role-helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import {getRoles, logRoles, getImplicitAriaRoles} from '../role-helpers'
import {render} from './helpers/test-utils'

beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => {})
})

afterEach(() => {
console.log.mockRestore()
})

function setup() {
const {getByTestId} = render(`
<section data-testid='a-section'>
Expand Down Expand Up @@ -137,16 +145,9 @@ test('getRoles returns expected roles for various dom nodes', () => {

test('logRoles calls console.log with output from prettyRoles', () => {
const {section} = setup()

jest.spyOn(console, 'log').mockImplementationOnce(() => {})

logRoles(section)
// eslint-disable-next-line no-console
expect(console.log).toHaveBeenCalledTimes(1)
// eslint-disable-next-line no-console
expect(console.log.mock.calls[0][0]).toMatchSnapshot()
// eslint-disable-next-line no-console
console.log.mockRestore()
})

test('getImplicitAriaRoles returns expected roles for various dom nodes', () => {
Expand All @@ -158,3 +159,5 @@ test('getImplicitAriaRoles returns expected roles for various dom nodes', () =>
expect(getImplicitAriaRoles(radio)).toEqual(['radio'])
expect(getImplicitAriaRoles(input)).toEqual(['textbox'])
})

/* eslint no-console:0 */
43 changes: 36 additions & 7 deletions src/pretty-dom.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,50 @@
import prettyFormat from 'pretty-format'
import {getDocument} from './helpers'

function inCypress(dom) {
const window =
(dom.ownerDocument && dom.ownerDocument.defaultView) || undefined
return (
(typeof global !== 'undefined' && global.Cypress) ||
(typeof window !== 'undefined' && window.Cypress)
)
}

const inNode = () =>
typeof process !== 'undefined' &&
process.versions !== undefined &&
process.versions.node !== undefined

const getMaxLength = dom =>
inCypress(dom) ? 0 : process.env.DEBUG_PRINT_LIMIT || 7000

const {DOMElement, DOMCollection} = prettyFormat.plugins

function prettyDOM(htmlElement, maxLength, options) {
if (htmlElement.documentElement) {
htmlElement = htmlElement.documentElement
function prettyDOM(
dom = getDocument().body,
Copy link
Member Author

@kentcdodds kentcdodds Aug 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone's not running in an environment where the document is globally available, then they have to provide a DOM node to log, otherwise we'll just default to document.body which I think is a good default. I imagine lots of people will just call logDOM() and that'll be helpful to them!

maxLength = getMaxLength(dom),
options,
) {
if (maxLength === 0) {
return ''
}
if (dom.documentElement) {
dom = dom.documentElement
}

const debugContent = prettyFormat(htmlElement, {
const debugContent = prettyFormat(dom, {
plugins: [DOMElement, DOMCollection],
printFunctionName: false,
highlight: true,
highlight: inNode(),
...options,
})
return maxLength !== undefined && htmlElement.outerHTML.length > maxLength
return maxLength !== undefined && dom.outerHTML.length > maxLength
? `${debugContent.slice(0, maxLength)}...`
: debugContent
}

export {prettyDOM}
const logDOM = (...args) => console.log(prettyDOM(...args))

export {prettyDOM, logDOM}

/* eslint no-console:0 */
28 changes: 1 addition & 27 deletions src/query-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,8 @@ import {prettyDOM} from './pretty-dom'
import {fuzzyMatches, matches, makeNormalizer} from './matches'
import {waitForElement} from './wait-for-element'

/* eslint-disable complexity */
function debugDOM(htmlElement) {
const limit = process.env.DEBUG_PRINT_LIMIT || 7000
alexkrolick marked this conversation as resolved.
Show resolved Hide resolved
const inNode =
typeof process !== 'undefined' &&
process.versions !== undefined &&
process.versions.node !== undefined
/* istanbul ignore next */
const window =
(htmlElement.ownerDocument && htmlElement.ownerDocument.defaultView) ||
undefined
const inCypress =
(typeof global !== 'undefined' && global.Cypress) ||
(typeof window !== 'undefined' && window.Cypress)
/* istanbul ignore else */
if (inCypress) {
return ''
} else if (inNode) {
return prettyDOM(htmlElement, limit)
} else {
return prettyDOM(htmlElement, limit, {highlight: false})
}
}
/* eslint-enable complexity */

function getElementError(message, container) {
return new Error([message, debugDOM(container)].filter(Boolean).join('\n\n'))
return new Error([message, prettyDOM(container)].filter(Boolean).join('\n\n'))
}

function getMultipleElementsFoundError(message, container) {
Expand Down Expand Up @@ -111,7 +86,6 @@ function buildQueries(queryAllBy, getMultipleError, getMissingError) {
}

export {
debugDOM,
getElementError,
getMultipleElementsFoundError,
queryAllByAttribute,
Expand Down
15 changes: 7 additions & 8 deletions src/role-helpers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {elementRoles} from 'aria-query'
import {debugDOM} from './query-helpers'
import {prettyDOM} from './pretty-dom'

const elementRoleList = buildElementRoleList(elementRoles)

Expand Down Expand Up @@ -73,24 +73,23 @@ function getRoles(container) {
}, {})
}

function prettyRoles(container) {
const roles = getRoles(container)
function prettyRoles(dom) {
const roles = getRoles(dom)

return Object.entries(roles)
.map(([role, elements]) => {
const delimiterBar = '-'.repeat(50)
const elementsString = elements
.map(el => debugDOM(el.cloneNode(false)))
.map(el => prettyDOM(el.cloneNode(false)))
.join('\n\n')

return `${role}:\n\n${elementsString}\n\n${delimiterBar}`
})
.join('\n')
}

function logRoles(container) {
// eslint-disable-next-line no-console
console.log(prettyRoles(container))
}
const logRoles = dom => console.log(prettyRoles(dom))

export {getRoles, logRoles, getImplicitAriaRoles, prettyRoles}

/* eslint no-console:0 */
4 changes: 2 additions & 2 deletions typings/query-helpers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type AllByAttribute = (

export const queryByAttribute: QueryByAttribute
export const queryAllByAttribute: AllByAttribute
export const debugDOM: (htmlElement: HTMLElement) => string
export const logDOM: (htmlElement: HTMLElement) => void
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the breaking change.

Impact is minimal because people probably aren't relying on this for their actual tests and it's more of a workflow method (which I doubt many people use).

That said, some libraries built on top of DTL may use this method, and the upgrade strategy for them is to switch from debugDOM to prettyDOM.

export const getElementError: (message: string, container: HTMLElement) => Error

/**
Expand Down Expand Up @@ -53,7 +53,7 @@ export type BuiltQueryMethods<Arguments extends any[]> = [
GetAllBy<Arguments>,
GetBy<Arguments>,
FindAllBy<Arguments>,
FindBy<Arguments>
FindBy<Arguments>,
]
export const buildQueries: <Arguments extends any[]>(
queryByAll: GetAllBy<Arguments>,
Expand Down