Skip to content

Commit 2273f03

Browse files
gnapseKent C. Dodds
authored and
Kent C. Dodds
committed
feat(getNodeText ): add getNodeText and ignore extra whitespace in node texts (testing-library#21)
* Ignore extra whitespace in all contexts when matching text * Expose getText * Properly expose getText * Rename getText as getNodeText * Document getNodeText in the README * Typo
1 parent 97af0e6 commit 2273f03

File tree

5 files changed

+41
-17
lines changed

5 files changed

+41
-17
lines changed

README.md

+19
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,25 @@ fireEvent.click(getElementByText('Submit'), rightClick)
395395
// default `button` property for click events is set to `0` which is a left click.
396396
```
397397

398+
#### `getNodeText(node: HTMLElement)`
399+
400+
Returns the complete text content of a html element, removing any extra
401+
whitespace. The intention is to treat text in nodes exactly as how it is
402+
perceived by users in a browser, where any extra whitespace within words in the
403+
html code is not meaningful when the text is rendered.
404+
405+
```javascript
406+
// <div>
407+
// Hello
408+
// World !
409+
// </div>
410+
const text = getNodeText(container.querySelector('div')) // "Hello World !"
411+
```
412+
413+
This function is also used internally when querying nodes by their text content.
414+
This enables functions like `getByText` and `queryByText` to work as expected,
415+
finding elements in the DOM similarly to how users would do.
416+
398417
## Custom Jest Matchers
399418

400419
When using [jest][], we recommend that you import a set of custom matchers that

src/get-node-text.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
function getNodeText(node) {
2+
return Array.from(node.childNodes)
3+
.filter(
4+
child => child.nodeType === Node.TEXT_NODE && Boolean(child.textContent),
5+
)
6+
.map(c => c.textContent)
7+
.join(' ')
8+
.trim()
9+
.replace(/\s+/g, ' ')
10+
}
11+
12+
export {getNodeText}

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ export * from './queries'
88
export * from './wait'
99
export * from './wait-for-element'
1010
export * from './matches'
11+
export * from './get-node-text'
1112
export * from './events'
1213
export * from './bind-element-to-queries'

src/matches.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ function matches(textToMatch, node, matcher) {
22
if (typeof textToMatch !== 'string') {
33
return false
44
}
5+
const normalizedText = textToMatch
6+
.toLowerCase()
7+
.trim()
8+
.replace(/\s+/g, ' ')
59
if (typeof matcher === 'string') {
6-
return textToMatch.toLowerCase().includes(matcher.toLowerCase())
10+
return normalizedText.includes(matcher.toLowerCase())
711
} else if (typeof matcher === 'function') {
8-
return matcher(textToMatch, node)
12+
return matcher(normalizedText, node)
913
} else {
10-
return matcher.test(textToMatch)
14+
return matcher.test(normalizedText)
1115
}
1216
}
1317

src/queries.js

+2-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import prettyFormat from 'pretty-format'
22
import {matches} from './matches'
3+
import {getNodeText} from './get-node-text'
34

45
const {DOMElement, DOMCollection} = prettyFormat.plugins
56

@@ -46,7 +47,7 @@ function queryByLabelText(container, text, {selector = '*'} = {}) {
4647
function queryByText(container, text, {selector = '*'} = {}) {
4748
return (
4849
Array.from(container.querySelectorAll(selector)).find(node =>
49-
matches(getText(node), node, text),
50+
matches(getNodeText(node), node, text),
5051
) || null
5152
)
5253
}
@@ -64,19 +65,6 @@ function queryByAttribute(attribute, container, text) {
6465
const queryByPlaceholderText = queryByAttribute.bind(null, 'placeholder')
6566
const queryByTestId = queryByAttribute.bind(null, 'data-testid')
6667

67-
// this is just a utility and not an exposed query.
68-
// There are no plans to expose this.
69-
function getText(node) {
70-
return Array.from(node.childNodes)
71-
.filter(
72-
child => child.nodeType === Node.TEXT_NODE && Boolean(child.textContent),
73-
)
74-
.map(c => c.textContent)
75-
.join(' ')
76-
.trim()
77-
.replace(/\s+/g, ' ')
78-
}
79-
8068
// getters
8169
// the reason we're not dynamically generating these functions that look so similar:
8270
// 1. The error messages are specific to each one and depend on arguments

0 commit comments

Comments
 (0)