From fe31e3460e8b40d45faf46b02b42e1f2981165c7 Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Sat, 8 Aug 2020 19:51:30 +0200 Subject: [PATCH 01/12] feat(get-user-trace): create utility to obtain the client's stack frame for debugging --- src/__tests__/get-user-trace.js | 43 +++++++++++++++++++++++++++++++++ src/get-user-trace.js | 19 +++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/__tests__/get-user-trace.js create mode 100644 src/get-user-trace.js diff --git a/src/__tests__/get-user-trace.js b/src/__tests__/get-user-trace.js new file mode 100644 index 00000000..81ccab7a --- /dev/null +++ b/src/__tests__/get-user-trace.js @@ -0,0 +1,43 @@ +import {getUserTrace} from '../get-user-trace' + +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 client error when frames from node_modules are first', () => { + const stack = `Error: Kaboom + at Object. (/home/john/projects/projects/sample-error/node_modules/@es2050/console/build/index.js:4:10) + at somethingWrong (/home/john/projects/sample-error/error-example.js:2:13) + ` + globalErrorMock.mockImplementationOnce(() => ({stack})) + const userTrace = getUserTrace(stack) + expect(userTrace).toEqual( + '/home/john/projects/sample-error/error-example.js:2:13\n', + ) +}) + +test('it returns only client error when node frames are present afterwards', () => { + const stack = `Error: Kaboom + at Object. (/home/john/projects/projects/sample-error/node_modules/@es2050/console/build/index.js:4:10) + at somethingWrong (/home/john/projects/sample-error/error-example.js:2:13) + at Object. (/home/user/Documents/projects/sample-error/error-example.js:14:1) + at Module._compile (internal/modules/cjs/loader.js:1151:30) + at Object.Module._extensions..js (internal/modules/cjs/loader.js:1171:10) + at Module.load (internal/modules/cjs/loader.js:1000:32) + at Function.Module._load (internal/modules/cjs/loader.js:899:14) + at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12) + at internal/main/run_main_module.js:17:47 + ` + globalErrorMock.mockImplementationOnce(() => ({stack})) + const userTrace = getUserTrace() + expect(userTrace).toEqual( + '/home/john/projects/sample-error/error-example.js:2:13\n', + ) +}) diff --git a/src/get-user-trace.js b/src/get-user-trace.js new file mode 100644 index 00000000..88987d4d --- /dev/null +++ b/src/get-user-trace.js @@ -0,0 +1,19 @@ +// Frame has the form "at myMethod (location/to/my/file.js:10:2)" +function getFrameLocation(frame) { + const locationStart = frame.indexOf('(') + 1 + const locationEnd = frame.indexOf(')') + + return frame.slice(locationStart, locationEnd) +} + +function getUserTrace() { + 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 `${getFrameLocation(firstClientCodeFrame)}\n` +} + +export {getUserTrace} From 9692ebe1ceb6dcf6aa8cdf6705a22db5592eb535 Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Sat, 8 Aug 2020 20:23:26 +0200 Subject: [PATCH 02/12] feat(pretty-dom): return user stack from pretty-dom --- src/__tests__/pretty-dom.js | 4 ++++ src/__tests__/screen.js | 4 ++++ src/pretty-dom.js | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/__tests__/pretty-dom.js b/src/__tests__/pretty-dom.js index fd297888..7dca0c22 100644 --- a/src/__tests__/pretty-dom.js +++ b/src/__tests__/pretty-dom.js @@ -1,6 +1,10 @@ import {prettyDOM, logDOM} from '../pretty-dom' import {render, renderIntoDocument} from './helpers/test-utils' +jest.mock('../get-user-trace', () => ({ + getUserTrace: () => '', +})) + beforeEach(() => { jest.spyOn(console, 'log').mockImplementation(() => {}) }) diff --git a/src/__tests__/screen.js b/src/__tests__/screen.js index 3a35bde8..9a8c3a3e 100644 --- a/src/__tests__/screen.js +++ b/src/__tests__/screen.js @@ -1,6 +1,10 @@ import {screen} from '..' import {renderIntoDocument} from './helpers/test-utils' +jest.mock('../get-user-trace', () => ({ + getUserTrace: () => '', +})) + beforeEach(() => { jest.spyOn(console, 'log').mockImplementation(() => {}) }) diff --git a/src/pretty-dom.js b/src/pretty-dom.js index 56e8e90d..af1475fc 100644 --- a/src/pretty-dom.js +++ b/src/pretty-dom.js @@ -1,4 +1,5 @@ import prettyFormat from 'pretty-format' +import {getUserTrace} from './get-user-trace' import {getDocument} from './helpers' function inCypress(dom) { @@ -61,6 +62,7 @@ function prettyDOM(dom, maxLength, options) { : debugContent } -const logDOM = (...args) => console.log(prettyDOM(...args)) +const logDOM = (...args) => + console.log(`${getUserTrace()}${prettyDOM(...args)}`) export {prettyDOM, logDOM} From 2c0797b4272948799c936f36cffde5e6470883fa Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Sat, 8 Aug 2020 20:34:47 +0200 Subject: [PATCH 03/12] feat(get-user-trace): add coloring to get-user-trace --- package.json | 5 +++-- src/__tests__/get-user-trace.js | 4 ++++ src/get-user-trace.js | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index caeb3bdd..8b9426f0 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "@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", @@ -76,4 +77,4 @@ "url": "https://github.com/testing-library/dom-testing-library/issues" }, "homepage": "https://github.com/testing-library/dom-testing-library#readme" -} +} \ No newline at end of file diff --git a/src/__tests__/get-user-trace.js b/src/__tests__/get-user-trace.js index 81ccab7a..408a7854 100644 --- a/src/__tests__/get-user-trace.js +++ b/src/__tests__/get-user-trace.js @@ -1,5 +1,9 @@ import {getUserTrace} from '../get-user-trace' +jest.mock('chalk', () => ({ + gray: msg => msg, +})) + let globalErrorMock beforeEach(() => { diff --git a/src/get-user-trace.js b/src/get-user-trace.js index 88987d4d..cb17fbd3 100644 --- a/src/get-user-trace.js +++ b/src/get-user-trace.js @@ -1,3 +1,5 @@ +import chalk from 'chalk' + // Frame has the form "at myMethod (location/to/my/file.js:10:2)" function getFrameLocation(frame) { const locationStart = frame.indexOf('(') + 1 @@ -13,7 +15,7 @@ function getUserTrace() { .slice(1) // Remove first line which has the form "Error: TypeError" .find(frame => !frame.includes('node_modules/')) // Ignore frames from 3rd party libraries - return `${getFrameLocation(firstClientCodeFrame)}\n` + return chalk.gray(`${getFrameLocation(firstClientCodeFrame)}\n`) } export {getUserTrace} From 3f02326881b658560d241a13f1f9d4fe350a7ad2 Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Sat, 15 Aug 2020 15:43:13 +0200 Subject: [PATCH 04/12] feat: instead of showing location of debug call, print full code frame --- package.json | 1 + src/__tests__/get-user-trace.js | 40 ++++++++++++++++++++++++--------- src/get-user-trace.js | 23 +++++++++++++++---- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 8b9426f0..70d3c7f6 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "types" ], "dependencies": { + "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.10.3", "@types/aria-query": "^4.2.0", "aria-query": "^4.2.2", diff --git a/src/__tests__/get-user-trace.js b/src/__tests__/get-user-trace.js index 408a7854..6a70f905 100644 --- a/src/__tests__/get-user-trace.js +++ b/src/__tests__/get-user-trace.js @@ -1,7 +1,19 @@ import {getUserTrace} from '../get-user-trace' -jest.mock('chalk', () => ({ - gray: msg => msg, +jest.mock('fs', () => ({ + readFileSync: () => ` + import { screen } from '@testing-library/dom' + + it('renders', () => { + document.body.appendChild( + document.createTextNode('Hello world') + ) + screen.debug() + + + expect(screen.getByText('Hello world')).toBeInTheDocument() + }) + `, })) let globalErrorMock @@ -18,19 +30,23 @@ afterEach(() => { test('it returns only client error when frames from node_modules are first', () => { const stack = `Error: Kaboom at Object. (/home/john/projects/projects/sample-error/node_modules/@es2050/console/build/index.js:4:10) - at somethingWrong (/home/john/projects/sample-error/error-example.js:2:13) + at somethingWrong (/home/john/projects/sample-error/error-example.js:8:7) ` globalErrorMock.mockImplementationOnce(() => ({stack})) const userTrace = getUserTrace(stack) - expect(userTrace).toEqual( - '/home/john/projects/sample-error/error-example.js:2:13\n', - ) + expect(userTrace).toMatchInlineSnapshot(` + " 6 | document.createTextNode('Hello world') + 7 | ) + > 8 | screen.debug() + | ^ + " + `) }) test('it returns only client error when node frames are present afterwards', () => { const stack = `Error: Kaboom at Object. (/home/john/projects/projects/sample-error/node_modules/@es2050/console/build/index.js:4:10) - at somethingWrong (/home/john/projects/sample-error/error-example.js:2:13) + at somethingWrong (/home/john/projects/sample-error/error-example.js:8:7) at Object. (/home/user/Documents/projects/sample-error/error-example.js:14:1) at Module._compile (internal/modules/cjs/loader.js:1151:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1171:10) @@ -41,7 +57,11 @@ test('it returns only client error when node frames are present afterwards', () ` globalErrorMock.mockImplementationOnce(() => ({stack})) const userTrace = getUserTrace() - expect(userTrace).toEqual( - '/home/john/projects/sample-error/error-example.js:2:13\n', - ) + expect(userTrace).toMatchInlineSnapshot(` + " 6 | document.createTextNode('Hello world') + 7 | ) + > 8 | screen.debug() + | ^ + " + `) }) diff --git a/src/get-user-trace.js b/src/get-user-trace.js index cb17fbd3..73563e2c 100644 --- a/src/get-user-trace.js +++ b/src/get-user-trace.js @@ -1,11 +1,26 @@ -import chalk from 'chalk' +import fs from 'fs' +import { codeFrameColumns } from '@babel/code-frame'; // Frame has the form "at myMethod (location/to/my/file.js:10:2)" -function getFrameLocation(frame) { +function getCodeFrame(frame) { const locationStart = frame.indexOf('(') + 1 const locationEnd = frame.indexOf(')') + const frameLocation = frame.slice(locationStart, locationEnd) - return 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 = fs.readFileSync(filename, 'utf-8') + } catch { + console.warn(`Couldn't read file ${filename} for displaying the code frame`) + } + return codeFrameColumns(rawFileContents, { + start: { line, column }, + }, { + highlightCode: true, + linesBelow: 0, + }) } function getUserTrace() { @@ -15,7 +30,7 @@ function getUserTrace() { .slice(1) // Remove first line which has the form "Error: TypeError" .find(frame => !frame.includes('node_modules/')) // Ignore frames from 3rd party libraries - return chalk.gray(`${getFrameLocation(firstClientCodeFrame)}\n`) + return `${getCodeFrame(firstClientCodeFrame)}\n` } export {getUserTrace} From 202abcd4dba96f2770663d5ff856e30c69113ee8 Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Sat, 15 Aug 2020 22:06:28 +0200 Subject: [PATCH 05/12] feat: conditionally load node dependencies and return empty if they can't be loaded --- src/__tests__/get-user-trace.js | 31 ++++++++++++++++-- src/get-user-trace.js | 57 +++++++++++++++++++++++++-------- 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/src/__tests__/get-user-trace.js b/src/__tests__/get-user-trace.js index 6a70f905..369e71f2 100644 --- a/src/__tests__/get-user-trace.js +++ b/src/__tests__/get-user-trace.js @@ -1,7 +1,9 @@ +import fs from 'fs' import {getUserTrace} from '../get-user-trace' jest.mock('fs', () => ({ - readFileSync: () => ` + readFileSync: jest.fn( + () => ` import { screen } from '@testing-library/dom' it('renders', () => { @@ -9,11 +11,12 @@ jest.mock('fs', () => ({ document.createTextNode('Hello world') ) screen.debug() - - + + expect(screen.getByText('Hello world')).toBeInTheDocument() }) `, + ), })) let globalErrorMock @@ -65,3 +68,25 @@ test('it returns only client error when node frames are present afterwards', () " `) }) + +test("it returns empty string if file from frame can't be read", () => { + const consoleWarnSpy = jest + .spyOn(global.console, 'warn') + .mockImplementationOnce(jest.fn) + + // Make fire read purposely fail + fs.readFileSync.mockImplementationOnce(() => { + throw Error() + }) + const filePath = '/home/john/projects/sample-error/error-example.js' + const stack = `Error: Kaboom + at somethingWrong (${filePath}:8:7) + ` + globalErrorMock.mockImplementationOnce(() => ({stack})) + + expect(getUserTrace(stack)).toEqual('') + expect(consoleWarnSpy).toHaveBeenCalledTimes(1) + expect(consoleWarnSpy).toHaveBeenCalledWith( + `Couldn't read file ${filePath} for displaying the code frame`, + ) +}) diff --git a/src/get-user-trace.js b/src/get-user-trace.js index 73563e2c..4d9df1ef 100644 --- a/src/get-user-trace.js +++ b/src/get-user-trace.js @@ -1,36 +1,65 @@ -import fs from 'fs' -import { codeFrameColumns } from '@babel/code-frame'; +// We try to load node dependencies +let readFileSync = null +let codeFrameColumns = null -// Frame has the form "at myMethod (location/to/my/file.js:10:2)" +try { + const nodeRequire = module && module.require + readFileSync = nodeRequire.call(module, 'fs').readFileSync + codeFrameColumns = nodeRequire.call(module, '@babel/code-frame') + .codeFrameColumns +} catch { + // We're in a browser environment + console.warn( + 'Printing the user trace is not supported 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 = "" + const frameLocationElements = frameLocation.split(':') + const [filename, line, column] = [ + frameLocationElements[0], + parseInt(frameLocationElements[1], 10), + parseInt(frameLocationElements[2], 10), + ] + + let rawFileContents = '' try { - rawFileContents = fs.readFileSync(filename, 'utf-8') + rawFileContents = readFileSync(filename, 'utf-8') } catch { console.warn(`Couldn't read file ${filename} for displaying the code frame`) + return '' } - return codeFrameColumns(rawFileContents, { - start: { line, column }, - }, { - highlightCode: true, - linesBelow: 0, - }) + + const codeFrame = codeFrameColumns( + rawFileContents, + { + start: {line, column}, + }, + { + highlightCode: true, + linesBelow: 0, + }, + ) + return `${codeFrame}\n` } function getUserTrace() { + // If we couldn't load dependencies, we can't generate a user trace + 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)}\n` + return getCodeFrame(firstClientCodeFrame) } export {getUserTrace} From 32cd44ce4b4cfcb85eaf752ee3397c57f12edd8d Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Sat, 15 Aug 2020 22:17:37 +0200 Subject: [PATCH 06/12] test: ignore coverage of catch when dependencies to render trace can't be loaded --- src/get-user-trace.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/get-user-trace.js b/src/get-user-trace.js index 4d9df1ef..5b0c75b3 100644 --- a/src/get-user-trace.js +++ b/src/get-user-trace.js @@ -9,6 +9,7 @@ try { .codeFrameColumns } catch { // We're in a browser environment + /* istanbul ignore next */ console.warn( 'Printing the user trace is not supported in a browser environment', ) @@ -50,6 +51,7 @@ function getCodeFrame(frame) { function getUserTrace() { // If we couldn't load dependencies, we can't generate a user trace + /* istanbul ignore next */ if (!readFileSync || !codeFrameColumns) { return '' } From 3be5856e754756bfcfdee12ba8ff3bfa7453b4fa Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Sun, 16 Aug 2020 11:01:04 +0200 Subject: [PATCH 07/12] test: refactor test names to be more expressive and move user code frame into variable --- src/__tests__/get-user-trace.js | 45 +++++++++++++++------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/__tests__/get-user-trace.js b/src/__tests__/get-user-trace.js index 369e71f2..3c48e3a0 100644 --- a/src/__tests__/get-user-trace.js +++ b/src/__tests__/get-user-trace.js @@ -2,23 +2,24 @@ import fs from 'fs' import {getUserTrace} from '../get-user-trace' 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 (/home/john/projects/sample-error/error-example.js:7:14)' + let globalErrorMock beforeEach(() => { @@ -30,46 +31,43 @@ afterEach(() => { global.Error.mockRestore() }) -test('it returns only client error when frames from node_modules are first', () => { +test('it returns only client code frame when code frames from node_modules are first', () => { const stack = `Error: Kaboom at Object. (/home/john/projects/projects/sample-error/node_modules/@es2050/console/build/index.js:4:10) - at somethingWrong (/home/john/projects/sample-error/error-example.js:8:7) + ${userStackFrame} ` globalErrorMock.mockImplementationOnce(() => ({stack})) const userTrace = getUserTrace(stack) + expect(userTrace).toMatchInlineSnapshot(` - " 6 | document.createTextNode('Hello world') - 7 | ) - > 8 | screen.debug() - | ^ + " 5 | document.createTextNode('Hello world') + 6 | ) + > 7 | screen.debug() + | ^ " `) }) -test('it returns only client error when node frames are present afterwards', () => { +test('it returns only client code frame when node code frames are present afterwards', () => { const stack = `Error: Kaboom at Object. (/home/john/projects/projects/sample-error/node_modules/@es2050/console/build/index.js:4:10) - at somethingWrong (/home/john/projects/sample-error/error-example.js:8:7) + ${userStackFrame} at Object. (/home/user/Documents/projects/sample-error/error-example.js:14:1) - at Module._compile (internal/modules/cjs/loader.js:1151:30) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:1171:10) - at Module.load (internal/modules/cjs/loader.js:1000:32) - at Function.Module._load (internal/modules/cjs/loader.js:899:14) - at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12) at internal/main/run_main_module.js:17:47 ` globalErrorMock.mockImplementationOnce(() => ({stack})) const userTrace = getUserTrace() + expect(userTrace).toMatchInlineSnapshot(` - " 6 | document.createTextNode('Hello world') - 7 | ) - > 8 | screen.debug() - | ^ + " 5 | document.createTextNode('Hello world') + 6 | ) + > 7 | screen.debug() + | ^ " `) }) -test("it returns empty string if file from frame can't be read", () => { +test("it returns empty string if file from code frame can't be read", () => { const consoleWarnSpy = jest .spyOn(global.console, 'warn') .mockImplementationOnce(jest.fn) @@ -78,15 +76,14 @@ test("it returns empty string if file from frame can't be read", () => { fs.readFileSync.mockImplementationOnce(() => { throw Error() }) - const filePath = '/home/john/projects/sample-error/error-example.js' const stack = `Error: Kaboom - at somethingWrong (${filePath}:8:7) + ${userStackFrame} ` globalErrorMock.mockImplementationOnce(() => ({stack})) expect(getUserTrace(stack)).toEqual('') expect(consoleWarnSpy).toHaveBeenCalledTimes(1) expect(consoleWarnSpy).toHaveBeenCalledWith( - `Couldn't read file ${filePath} for displaying the code frame`, + `Couldn't read file /home/john/projects/sample-error/error-example.js for displaying the code frame`, ) }) From 514f8415522c93d3ff4497d5fc503297a7a816cd Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Sun, 16 Aug 2020 11:05:16 +0200 Subject: [PATCH 08/12] refactor: rename get-user-trace to get-user-code-frame --- .../{get-user-trace.js => get-user-code-frame.js} | 10 +++++----- src/__tests__/pretty-dom.js | 4 ++-- src/__tests__/screen.js | 5 +++-- src/{get-user-trace.js => get-user-code-frame.js} | 4 ++-- src/pretty-dom.js | 4 ++-- 5 files changed, 14 insertions(+), 13 deletions(-) rename src/__tests__/{get-user-trace.js => get-user-code-frame.js} (91%) rename src/{get-user-trace.js => get-user-code-frame.js} (96%) diff --git a/src/__tests__/get-user-trace.js b/src/__tests__/get-user-code-frame.js similarity index 91% rename from src/__tests__/get-user-trace.js rename to src/__tests__/get-user-code-frame.js index 3c48e3a0..7c634a4b 100644 --- a/src/__tests__/get-user-trace.js +++ b/src/__tests__/get-user-code-frame.js @@ -1,11 +1,11 @@ import fs from 'fs' -import {getUserTrace} from '../get-user-trace' +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' + import {screen} from '@testing-library/dom' it('renders', () => { document.body.appendChild( document.createTextNode('Hello world') @@ -37,7 +37,7 @@ test('it returns only client code frame when code frames from node_modules are f ${userStackFrame} ` globalErrorMock.mockImplementationOnce(() => ({stack})) - const userTrace = getUserTrace(stack) + const userTrace = getUserCodeFrame(stack) expect(userTrace).toMatchInlineSnapshot(` " 5 | document.createTextNode('Hello world') @@ -56,7 +56,7 @@ test('it returns only client code frame when node code frames are present afterw at internal/main/run_main_module.js:17:47 ` globalErrorMock.mockImplementationOnce(() => ({stack})) - const userTrace = getUserTrace() + const userTrace = getUserCodeFrame() expect(userTrace).toMatchInlineSnapshot(` " 5 | document.createTextNode('Hello world') @@ -81,7 +81,7 @@ test("it returns empty string if file from code frame can't be read", () => { ` globalErrorMock.mockImplementationOnce(() => ({stack})) - expect(getUserTrace(stack)).toEqual('') + expect(getUserCodeFrame(stack)).toEqual('') expect(consoleWarnSpy).toHaveBeenCalledTimes(1) expect(consoleWarnSpy).toHaveBeenCalledWith( `Couldn't read file /home/john/projects/sample-error/error-example.js for displaying the code frame`, diff --git a/src/__tests__/pretty-dom.js b/src/__tests__/pretty-dom.js index 7dca0c22..59f2eda7 100644 --- a/src/__tests__/pretty-dom.js +++ b/src/__tests__/pretty-dom.js @@ -1,8 +1,8 @@ import {prettyDOM, logDOM} from '../pretty-dom' import {render, renderIntoDocument} from './helpers/test-utils' -jest.mock('../get-user-trace', () => ({ - getUserTrace: () => '', +jest.mock('../get-user-code-frame', () => ({ + getUserCodeFrame: () => '', })) beforeEach(() => { diff --git a/src/__tests__/screen.js b/src/__tests__/screen.js index 9a8c3a3e..2496e09f 100644 --- a/src/__tests__/screen.js +++ b/src/__tests__/screen.js @@ -1,8 +1,9 @@ import {screen} from '..' import {renderIntoDocument} from './helpers/test-utils' -jest.mock('../get-user-trace', () => ({ - getUserTrace: () => '', +// Since screen.debug internally calls getUserCodeFrame, we mock it so it doesn't affect these tests +jest.mock('../get-user-code-frame', () => ({ + getUserCodeFrame: () => '', })) beforeEach(() => { diff --git a/src/get-user-trace.js b/src/get-user-code-frame.js similarity index 96% rename from src/get-user-trace.js rename to src/get-user-code-frame.js index 5b0c75b3..55a47cfa 100644 --- a/src/get-user-trace.js +++ b/src/get-user-code-frame.js @@ -49,7 +49,7 @@ function getCodeFrame(frame) { return `${codeFrame}\n` } -function getUserTrace() { +function getUserCodeFrame() { // If we couldn't load dependencies, we can't generate a user trace /* istanbul ignore next */ if (!readFileSync || !codeFrameColumns) { @@ -64,4 +64,4 @@ function getUserTrace() { return getCodeFrame(firstClientCodeFrame) } -export {getUserTrace} +export {getUserCodeFrame} diff --git a/src/pretty-dom.js b/src/pretty-dom.js index af1475fc..f480d0b7 100644 --- a/src/pretty-dom.js +++ b/src/pretty-dom.js @@ -1,5 +1,5 @@ import prettyFormat from 'pretty-format' -import {getUserTrace} from './get-user-trace' +import {getUserCodeFrame} from './get-user-code-frame' import {getDocument} from './helpers' function inCypress(dom) { @@ -63,6 +63,6 @@ function prettyDOM(dom, maxLength, options) { } const logDOM = (...args) => - console.log(`${getUserTrace()}${prettyDOM(...args)}`) + console.log(`${getUserCodeFrame()}${prettyDOM(...args)}`) export {prettyDOM, logDOM} From 859aa7edcf1764e7131d1f3c127513a83e3d9ff7 Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Sun, 16 Aug 2020 15:48:26 +0200 Subject: [PATCH 09/12] feat: display location on top of code frame --- src/__tests__/get-user-code-frame.js | 6 +++-- src/__tests__/pretty-dom.js | 35 +++++++++++++++++++++++++--- src/get-user-code-frame.js | 2 +- src/pretty-dom.js | 10 ++++++-- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/__tests__/get-user-code-frame.js b/src/__tests__/get-user-code-frame.js index 7c634a4b..3f20f124 100644 --- a/src/__tests__/get-user-code-frame.js +++ b/src/__tests__/get-user-code-frame.js @@ -40,7 +40,8 @@ test('it returns only client code frame when code frames from node_modules are f const userTrace = getUserCodeFrame(stack) expect(userTrace).toMatchInlineSnapshot(` - " 5 | document.createTextNode('Hello world') + "/home/john/projects/sample-error/error-example.js:7:14 + 5 | document.createTextNode('Hello world') 6 | ) > 7 | screen.debug() | ^ @@ -59,7 +60,8 @@ test('it returns only client code frame when node code frames are present afterw const userTrace = getUserCodeFrame() expect(userTrace).toMatchInlineSnapshot(` - " 5 | document.createTextNode('Hello world') + "/home/john/projects/sample-error/error-example.js:7:14 + 5 | document.createTextNode('Hello world') 6 | ) > 7 | screen.debug() | ^ diff --git a/src/__tests__/pretty-dom.js b/src/__tests__/pretty-dom.js index 59f2eda7..1d1ce79b 100644 --- a/src/__tests__/pretty-dom.js +++ b/src/__tests__/pretty-dom.js @@ -1,9 +1,8 @@ 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', () => ({ - getUserCodeFrame: () => '', -})) +jest.mock('../get-user-code-frame') beforeEach(() => { jest.spyOn(console, 'log').mockImplementation(() => {}) @@ -64,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('
Hello World!
') + logDOM(container) + expect(console.log).toHaveBeenCalledTimes(1) + expect(console.log.mock.calls[0][0]).toMatchInlineSnapshot(` + "
+
+ Hello World! +
+
+ + "/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( diff --git a/src/get-user-code-frame.js b/src/get-user-code-frame.js index 55a47cfa..b47a56fb 100644 --- a/src/get-user-code-frame.js +++ b/src/get-user-code-frame.js @@ -46,7 +46,7 @@ function getCodeFrame(frame) { linesBelow: 0, }, ) - return `${codeFrame}\n` + return `${frameLocation}\n${codeFrame}\n` } function getUserCodeFrame() { diff --git a/src/pretty-dom.js b/src/pretty-dom.js index f480d0b7..eed80e7b 100644 --- a/src/pretty-dom.js +++ b/src/pretty-dom.js @@ -62,7 +62,13 @@ function prettyDOM(dom, maxLength, options) { : debugContent } -const logDOM = (...args) => - console.log(`${getUserCodeFrame()}${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} From b108fe0994d6d813a7f8e247b7d06f2ac7cba57e Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Sun, 16 Aug 2020 20:45:16 +0200 Subject: [PATCH 10/12] feat: make frame location color dimmer --- src/get-user-code-frame.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/get-user-code-frame.js b/src/get-user-code-frame.js index b47a56fb..f5c8f5bc 100644 --- a/src/get-user-code-frame.js +++ b/src/get-user-code-frame.js @@ -1,12 +1,15 @@ // 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 /* istanbul ignore next */ @@ -46,7 +49,7 @@ function getCodeFrame(frame) { linesBelow: 0, }, ) - return `${frameLocation}\n${codeFrame}\n` + return `${chalk.dim(frameLocation)}\n${codeFrame}\n` } function getUserCodeFrame() { From c47fca6fd132e7463522cce473fbcbcc88bb1867 Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Thu, 20 Aug 2020 21:04:12 +0200 Subject: [PATCH 11/12] test: shorten paths used as test data for get user code frame --- src/__tests__/get-user-code-frame.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/__tests__/get-user-code-frame.js b/src/__tests__/get-user-code-frame.js index 3f20f124..3d9a785d 100644 --- a/src/__tests__/get-user-code-frame.js +++ b/src/__tests__/get-user-code-frame.js @@ -17,8 +17,7 @@ jest.mock('fs', () => ({ ), })) -const userStackFrame = - 'at somethingWrong (/home/john/projects/sample-error/error-example.js:7:14)' +const userStackFrame = 'at somethingWrong (/sample-error/error-example.js:7:14)' let globalErrorMock @@ -31,16 +30,16 @@ afterEach(() => { global.Error.mockRestore() }) -test('it returns only client code frame when code frames from node_modules are first', () => { +test('it returns only user code frame when code frames from node_modules are first', () => { const stack = `Error: Kaboom - at Object. (/home/john/projects/projects/sample-error/node_modules/@es2050/console/build/index.js:4:10) + at Object. (/sample-error/node_modules/@es2050/console/build/index.js:4:10) ${userStackFrame} ` globalErrorMock.mockImplementationOnce(() => ({stack})) const userTrace = getUserCodeFrame(stack) expect(userTrace).toMatchInlineSnapshot(` - "/home/john/projects/sample-error/error-example.js:7:14 + "/sample-error/error-example.js:7:14 5 | document.createTextNode('Hello world') 6 | ) > 7 | screen.debug() @@ -49,18 +48,18 @@ test('it returns only client code frame when code frames from node_modules are f `) }) -test('it returns only client code frame when node code frames are present afterwards', () => { +test('it returns only user code frame when node code frames are present afterwards', () => { const stack = `Error: Kaboom - at Object. (/home/john/projects/projects/sample-error/node_modules/@es2050/console/build/index.js:4:10) + at Object. (/sample-error/node_modules/@es2050/console/build/index.js:4:10) ${userStackFrame} - at Object. (/home/user/Documents/projects/sample-error/error-example.js:14:1) + at Object. (/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(` - "/home/john/projects/sample-error/error-example.js:7:14 + "/sample-error/error-example.js:7:14 5 | document.createTextNode('Hello world') 6 | ) > 7 | screen.debug() @@ -86,6 +85,6 @@ test("it returns empty string if file from code frame can't be read", () => { expect(getUserCodeFrame(stack)).toEqual('') expect(consoleWarnSpy).toHaveBeenCalledTimes(1) expect(consoleWarnSpy).toHaveBeenCalledWith( - `Couldn't read file /home/john/projects/sample-error/error-example.js for displaying the code frame`, + `Couldn't read file /sample-error/error-example.js for displaying the code frame`, ) }) From 6c6df902841fd9afbb166e8355b5f912d221cd08 Mon Sep 17 00:00:00 2001 From: Victor Cordova Date: Thu, 20 Aug 2020 21:11:09 +0200 Subject: [PATCH 12/12] feat: remove warnings from get user code frame --- src/__tests__/get-user-code-frame.js | 8 -------- src/get-user-code-frame.js | 7 +------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/__tests__/get-user-code-frame.js b/src/__tests__/get-user-code-frame.js index 3d9a785d..3b72fe10 100644 --- a/src/__tests__/get-user-code-frame.js +++ b/src/__tests__/get-user-code-frame.js @@ -69,10 +69,6 @@ test('it returns only user code frame when node code frames are present afterwar }) test("it returns empty string if file from code frame can't be read", () => { - const consoleWarnSpy = jest - .spyOn(global.console, 'warn') - .mockImplementationOnce(jest.fn) - // Make fire read purposely fail fs.readFileSync.mockImplementationOnce(() => { throw Error() @@ -83,8 +79,4 @@ test("it returns empty string if file from code frame can't be read", () => { globalErrorMock.mockImplementationOnce(() => ({stack})) expect(getUserCodeFrame(stack)).toEqual('') - expect(consoleWarnSpy).toHaveBeenCalledTimes(1) - expect(consoleWarnSpy).toHaveBeenCalledWith( - `Couldn't read file /sample-error/error-example.js for displaying the code frame`, - ) }) diff --git a/src/get-user-code-frame.js b/src/get-user-code-frame.js index f5c8f5bc..7cdb90b6 100644 --- a/src/get-user-code-frame.js +++ b/src/get-user-code-frame.js @@ -12,10 +12,6 @@ try { chalk = nodeRequire.call(module, 'chalk') } catch { // We're in a browser environment - /* istanbul ignore next */ - console.warn( - 'Printing the user trace is not supported in a browser environment', - ) } // frame has the form "at myMethod (location/to/my/file.js:10:2)" @@ -35,7 +31,6 @@ function getCodeFrame(frame) { try { rawFileContents = readFileSync(filename, 'utf-8') } catch { - console.warn(`Couldn't read file ${filename} for displaying the code frame`) return '' } @@ -53,7 +48,7 @@ function getCodeFrame(frame) { } function getUserCodeFrame() { - // If we couldn't load dependencies, we can't generate a user trace + // If we couldn't load dependencies, we can't generate the user trace /* istanbul ignore next */ if (!readFileSync || !codeFrameColumns) { return ''