diff --git a/README.md b/README.md index 69809c09..85ce9bee 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ when a real user uses it. * [Custom Jest Matchers - Typescript](#custom-jest-matchers---typescript) * [`TextMatch`](#textmatch) * [`query` APIs](#query-apis) +* [Debugging](#debugging) * [Implementations](#implementations) * [FAQ](#faq) * [Other Solutions](#other-solutions) @@ -540,6 +541,42 @@ expect(submitButton).toBeNull() // it doesn't exist expect(submitButton).not.toBeInTheDOM() ``` +## Debugging + +When you use any `get` calls in your test cases, the current state of the `container` +(DOM) gets printed on the console. For example: + +```javascript +//
Hello world
+getByText(container, 'Goodbye world') // will fail by throwing error +``` + +The above test case will fail, however it prints the state of your DOM under test, +so you will get to see: + +``` +Unable to find an element with the text: Goodbye world. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. +Here is the state of your container: +
+
+ Hello World! +
+
+``` + +Note: Since the DOM size can get really large, you can set the limit of DOM content +to be printed via environment variable `DEBUG_PRINT_LIMIT`. The default value is +`7000`. You will see `...` in the console, when the DOM content is stripped off, +because of the length you have set or due to default size limit. Here's how you +might increase this limit when running tests: + +``` +DEBUG_PRINT_LIMIT=10000 npm test +``` + +This works on macOS/linux, you'll need to do something else for windows. If you'd +like a solution that works for both, see [`cross-env`](https://www.npmjs.com/package/cross-env) + ## Implementations This library was not built to be used on its own. The original implementation diff --git a/package.json b/package.json index 1ba1cfee..c6f7fc92 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ ], "dependencies": { "jest-matcher-utils": "^22.4.3", + "pretty-format": "^22.4.3", "mutationobserver-shim": "^0.3.2", "wait-for-expect": "^0.4.0" }, diff --git a/src/__tests__/__snapshots__/element-queries.js.snap b/src/__tests__/__snapshots__/element-queries.js.snap index 6d636275..a026cde0 100644 --- a/src/__tests__/__snapshots__/element-queries.js.snap +++ b/src/__tests__/__snapshots__/element-queries.js.snap @@ -1,15 +1,59 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`get throws a useful error message 1`] = `"Unable to find a label with the text of: LucyRicardo"`; +exports[`get throws a useful error message 1`] = ` +"Unable to find a label with the text of: LucyRicardo -exports[`get throws a useful error message 2`] = `"Unable to find an element with the placeholder text of: LucyRicardo"`; +
 + 
 +
" +`; -exports[`get throws a useful error message 3`] = `"Unable to find an element with the text: LucyRicardo. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible."`; +exports[`get throws a useful error message 2`] = ` +"Unable to find an element with the placeholder text of: LucyRicardo -exports[`get throws a useful error message 4`] = `"Unable to find an element by: [data-testid=\\"LucyRicardo\\"]"`; +
 + 
 +
" +`; -exports[`get throws a useful error message 5`] = `"Unable to find an element with the alt text: LucyRicardo"`; +exports[`get throws a useful error message 3`] = ` +"Unable to find an element with the text: LucyRicardo. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. -exports[`label with no form control 1`] = `"Found a label with the text of: alone, however no form control was found associated to that label. Make sure you're using the \\"for\\" attribute or \\"aria-labelledby\\" attribute correctly."`; +
 + 
 +
" +`; -exports[`totally empty label 1`] = `"Found a label with the text of: , however no form control was found associated to that label. Make sure you're using the \\"for\\" attribute or \\"aria-labelledby\\" attribute correctly."`; +exports[`get throws a useful error message 4`] = ` +"Unable to find an element by: [data-testid=\\"LucyRicardo\\"] + +
 + 
 +
" +`; + +exports[`get throws a useful error message 5`] = ` +"Unable to find an element with the alt text: LucyRicardo + +
 + 
 +
" +`; + +exports[`label with no form control 1`] = ` +"Found a label with the text of: alone, however no form control was found associated to that label. Make sure you're using the \\"for\\" attribute or \\"aria-labelledby\\" attribute correctly. + +
 +  +
" +`; + +exports[`totally empty label 1`] = ` +"Found a label with the text of: , however no form control was found associated to that label. Make sure you're using the \\"for\\" attribute or \\"aria-labelledby\\" attribute correctly. + +
 + 
" +`; diff --git a/src/__tests__/__snapshots__/wait-for-element.js.snap b/src/__tests__/__snapshots__/wait-for-element.js.snap index 9ca53ee7..aa5286c1 100644 --- a/src/__tests__/__snapshots__/wait-for-element.js.snap +++ b/src/__tests__/__snapshots__/wait-for-element.js.snap @@ -1,8 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[ - `it returns immediately if the callback returns the value before any mutations 1` -] = ` +exports[`it returns immediately if the callback returns the value before any mutations 1`] = `
@@ -10,39 +8,37 @@ exports[ data-testid="initial-element" />
-` +`; exports[`it throws if timeout is exceeded 1`] = ` Array [ [Error: Timed out in waitForElement.], ] -` +`; exports[`it throws if timeout is exceeded 2`] = `
-` +`; -exports[ - `it throws the same error that the callback has thrown if timeout is exceeded 1` -] = ` +exports[`it throws the same error that the callback has thrown if timeout is exceeded 1`] = ` Array [ - [Error: Unable to find an element by: [data-testid="test"]], + [Error: Unable to find an element by: [data-testid="test"] + +], ] -` +`; -exports[ - `it throws the same error that the callback has thrown if timeout is exceeded 2` -] = ` +exports[`it throws the same error that the callback has thrown if timeout is exceeded 2`] = `
-` +`; -exports[ - `it waits for the callback to return a value and only reacts to DOM mutations 1` -] = ` +exports[`it waits for the callback to return a value and only reacts to DOM mutations 1`] = `
-` +`; exports[`it waits for the next DOM mutation with default callback 1`] = `
-` +`; exports[`it waits for the next DOM mutation with default callback 2`] = `
-` +`; diff --git a/src/__tests__/element-queries.js b/src/__tests__/element-queries.js index 33ff6100..e84d6f8b 100644 --- a/src/__tests__/element-queries.js +++ b/src/__tests__/element-queries.js @@ -193,4 +193,36 @@ test('using jest helpers to check element class names', () => { ).toThrowError() }) +test('test the debug helper prints the dom state here', () => { + const originalDebugPrintLimit = process.env.DEBUG_PRINT_LIMIT + const Large = `
+ ${Array.from({length: 7000}, (v, key) => key).map(() => { + return `
+ Hello World! +
` + })} +
` + + const {getByText} = render(Large) // render large DOM which exceeds 7000 limit + expect(() => expect(getByText('not present')).toBeInTheDOM()).toThrowError() + + const Hello = `
+ Hello World! +
` + const {getByTestId} = render(Hello) + process.env.DEBUG_PRINT_LIMIT = 5 // user should see `...` + expect(() => expect(getByTestId('not present')).toBeInTheDOM()).toThrowError( + /\.\.\./, + ) + + const {getByLabelText} = render(Hello) + process.env.DEBUG_PRINT_LIMIT = 10000 // user shouldn't see `...` + expect(() => + expect(getByLabelText('not present')).toBeInTheDOM(/^((?!\.\.\.).)*$/), + ).toThrowError() + + //all good replacing it with old value + process.env.DEBUG_PRINT_LIMIT = originalDebugPrintLimit +}) + /* eslint jsx-a11y/label-has-for:0 */ diff --git a/src/queries.js b/src/queries.js index bddf781a..86b1a529 100644 --- a/src/queries.js +++ b/src/queries.js @@ -1,5 +1,8 @@ +import prettyFormat from 'pretty-format' import {matches} from './matches' +const {DOMElement, DOMCollection} = prettyFormat.plugins + // Here are the queries for the library. // The queries here should only be things that are accessible to both users who are using a screen reader // and those who are not using a screen reader (with the exception of the data-testid attribute query). @@ -78,7 +81,11 @@ function getText(node) { function getByTestId(container, id, ...rest) { const el = queryByTestId(container, id, ...rest) if (!el) { - throw new Error(`Unable to find an element by: [data-testid="${id}"]`) + throw new Error( + `Unable to find an element by: [data-testid="${id}"] \n\n${htmlElementToDisplay( + container, + )}`, + ) } return el } @@ -87,7 +94,9 @@ function getByPlaceholderText(container, text, ...rest) { const el = queryByPlaceholderText(container, text, ...rest) if (!el) { throw new Error( - `Unable to find an element with the placeholder text of: ${text}`, + `Unable to find an element with the placeholder text of: ${text} \n\n${htmlElementToDisplay( + container, + )}`, ) } return el @@ -99,10 +108,16 @@ function getByLabelText(container, text, ...rest) { const label = queryLabelByText(container, text) if (label) { throw new Error( - `Found a label with the text of: ${text}, however no form control was found associated to that label. Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly.`, + `Found a label with the text of: ${text}, however no form control was found associated to that label. Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly. \n\n${htmlElementToDisplay( + container, + )}`, ) } else { - throw new Error(`Unable to find a label with the text of: ${text}`) + throw new Error( + `Unable to find a label with the text of: ${text} \n\n${htmlElementToDisplay( + container, + )}`, + ) } } return el @@ -112,7 +127,9 @@ function getByText(container, text, ...rest) { const el = queryByText(container, text, ...rest) if (!el) { throw new Error( - `Unable to find an element with the text: ${text}. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.`, + `Unable to find an element with the text: ${text}. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. \n\n${htmlElementToDisplay( + container, + )}`, ) } return el @@ -129,11 +146,27 @@ function queryByAltText(container, alt) { function getByAltText(container, alt) { const el = queryByAltText(container, alt) if (!el) { - throw new Error(`Unable to find an element with the alt text: ${alt}`) + throw new Error( + `Unable to find an element with the alt text: ${alt} \n\n${htmlElementToDisplay( + container, + )}`, + ) } return el } +function htmlElementToDisplay(htmlElement) { + const debugContent = prettyFormat(htmlElement, { + plugins: [DOMElement, DOMCollection], + printFunctionName: false, + highlight: true, + }) + const maxLength = process.env.DEBUG_PRINT_LIMIT || 7000 + return htmlElement.outerHTML.length > maxLength + ? `${debugContent.slice(0, maxLength)}...` + : debugContent +} + export { queryByPlaceholderText, getByPlaceholderText,