Skip to content

Pr/440 show line in debug #733

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

Merged
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
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@
"types"
],
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.10.3",
"@types/aria-query": "^4.2.0",
"aria-query": "^4.2.2",
"dom-accessibility-api": "^0.5.1",
"pretty-format": "^26.4.2"
"pretty-format": "^26.4.2",
"chalk": "^4.1.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.10.1",
Expand Down Expand Up @@ -76,4 +78,4 @@
"url": "https://github.com/testing-library/dom-testing-library/issues"
},
"homepage": "https://github.com/testing-library/dom-testing-library#readme"
}
}
82 changes: 82 additions & 0 deletions src/__tests__/get-user-code-frame.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import fs from 'fs'
import {getUserCodeFrame} from '../get-user-code-frame'

jest.mock('fs', () => ({
// We setup the contents of a sample file
readFileSync: jest.fn(
() => `
import {screen} from '@testing-library/dom'
it('renders', () => {
document.body.appendChild(
document.createTextNode('Hello world')
)
screen.debug()
expect(screen.getByText('Hello world')).toBeInTheDocument()
})
`,
),
}))

const userStackFrame = 'at somethingWrong (/sample-error/error-example.js:7:14)'

let globalErrorMock

beforeEach(() => {
// Mock global.Error so we can setup our own stack messages
globalErrorMock = jest.spyOn(global, 'Error')
})

afterEach(() => {
global.Error.mockRestore()
})

test('it returns only user code frame when code frames from node_modules are first', () => {
const stack = `Error: Kaboom
at Object.<anonymous> (/sample-error/node_modules/@es2050/console/build/index.js:4:10)
${userStackFrame}
`
globalErrorMock.mockImplementationOnce(() => ({stack}))
const userTrace = getUserCodeFrame(stack)

expect(userTrace).toMatchInlineSnapshot(`
"/sample-error/error-example.js:7:14
5 | document.createTextNode('Hello world')
6 | )
> 7 | screen.debug()
| ^
"
`)
})

test('it returns only user code frame when node code frames are present afterwards', () => {
const stack = `Error: Kaboom
at Object.<anonymous> (/sample-error/node_modules/@es2050/console/build/index.js:4:10)
${userStackFrame}
at Object.<anonymous> (/sample-error/error-example.js:14:1)
at internal/main/run_main_module.js:17:47
`
globalErrorMock.mockImplementationOnce(() => ({stack}))
const userTrace = getUserCodeFrame()

expect(userTrace).toMatchInlineSnapshot(`
"/sample-error/error-example.js:7:14
5 | document.createTextNode('Hello world')
6 | )
> 7 | screen.debug()
| ^
"
`)
})

test("it returns empty string if file from code frame can't be read", () => {
// Make fire read purposely fail
fs.readFileSync.mockImplementationOnce(() => {
throw Error()
})
const stack = `Error: Kaboom
${userStackFrame}
`
globalErrorMock.mockImplementationOnce(() => ({stack}))

expect(getUserCodeFrame(stack)).toEqual('')
})
33 changes: 33 additions & 0 deletions src/__tests__/pretty-dom.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {prettyDOM, logDOM} from '../pretty-dom'
import {getUserCodeFrame} from '../get-user-code-frame'
import {render, renderIntoDocument} from './helpers/test-utils'

jest.mock('../get-user-code-frame')

beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => {})
})
Expand Down Expand Up @@ -60,6 +63,36 @@ test('logDOM logs prettyDOM to the console', () => {
`)
})

test('logDOM logs prettyDOM with code frame to the console', () => {
getUserCodeFrame.mockImplementationOnce(
() => `"/home/john/projects/sample-error/error-example.js:7:14
5 | document.createTextNode('Hello World!')
6 | )
> 7 | screen.debug()
| ^
"
`,
)
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>

"/home/john/projects/sample-error/error-example.js:7:14
5 | document.createTextNode('Hello World!')
6 | )
> 7 | screen.debug()
| ^
"
"
`)
})

describe('prettyDOM fails with first parameter without outerHTML field', () => {
test('with array', () => {
expect(() => prettyDOM(['outerHTML'])).toThrowErrorMatchingInlineSnapshot(
Expand Down
5 changes: 5 additions & 0 deletions src/__tests__/screen.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import {screen} from '..'
import {renderIntoDocument} from './helpers/test-utils'

// Since screen.debug internally calls getUserCodeFrame, we mock it so it doesn't affect these tests
jest.mock('../get-user-code-frame', () => ({
getUserCodeFrame: () => '',
}))

beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => {})
})
Expand Down
65 changes: 65 additions & 0 deletions src/get-user-code-frame.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// We try to load node dependencies
let chalk = null
let readFileSync = null
let codeFrameColumns = null

try {
const nodeRequire = module && module.require

readFileSync = nodeRequire.call(module, 'fs').readFileSync
codeFrameColumns = nodeRequire.call(module, '@babel/code-frame')
.codeFrameColumns
chalk = nodeRequire.call(module, 'chalk')
} catch {
// We're in a browser environment
}

// frame has the form "at myMethod (location/to/my/file.js:10:2)"
function getCodeFrame(frame) {
const locationStart = frame.indexOf('(') + 1
const locationEnd = frame.indexOf(')')
const frameLocation = frame.slice(locationStart, locationEnd)

const frameLocationElements = frameLocation.split(':')
const [filename, line, column] = [
frameLocationElements[0],
parseInt(frameLocationElements[1], 10),
parseInt(frameLocationElements[2], 10),
]

let rawFileContents = ''
try {
rawFileContents = readFileSync(filename, 'utf-8')
} catch {
return ''
}

const codeFrame = codeFrameColumns(
rawFileContents,
{
start: {line, column},
},
{
highlightCode: true,
linesBelow: 0,
},
)
return `${chalk.dim(frameLocation)}\n${codeFrame}\n`
}

function getUserCodeFrame() {
// If we couldn't load dependencies, we can't generate the user trace
/* istanbul ignore next */
if (!readFileSync || !codeFrameColumns) {
return ''
}
const err = new Error()
const firstClientCodeFrame = err.stack
.split('\n')
.slice(1) // Remove first line which has the form "Error: TypeError"
.find(frame => !frame.includes('node_modules/')) // Ignore frames from 3rd party libraries

return getCodeFrame(firstClientCodeFrame)
}

export {getUserCodeFrame}
10 changes: 9 additions & 1 deletion src/pretty-dom.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import prettyFormat from 'pretty-format'
import {getUserCodeFrame} from './get-user-code-frame'
import {getDocument} from './helpers'

function inCypress(dom) {
Expand Down Expand Up @@ -61,6 +62,13 @@ function prettyDOM(dom, maxLength, options) {
: debugContent
}

const logDOM = (...args) => console.log(prettyDOM(...args))
const logDOM = (...args) => {
const userCodeFrame = getUserCodeFrame()
if (userCodeFrame) {
console.log(`${prettyDOM(...args)}\n\n${userCodeFrame}`)
} else {
console.log(prettyDOM(...args))
}
}

export {prettyDOM, logDOM}