Skip to content

Commit d347302

Browse files
chore: Release v8 as stable (#979)
BREAKING CHANGE: Remove deprecated `waitFormDOMChange` BREAKING CHANGE: Remove deprecated `waitForElement` BREAKING CHANGE: The `timeout` in `waitFor(callback, { interval, timeout } )` now uses the same clock as `interval`. Previously `timeout` was always using the real clock while `interval` was using the global clock which could've been mocked out. For the old behavior I'd recommend `waitFor(callback, { interval, timeout: Number.POSITIVE_INFINITY })` and rely on your test runner to timeout considering real timers. BREAKING CHANGE: `<script />`, `<style />` and comment nodes are now ignored by default in `prettyDOM` .If you whish to return to the old behavior, use a custom `filterNode` function. In this case `prettyDOM(element, { filterNode: () => true })`. BREAKING CHANGE: node 10 is no longer supported. It reached its end-of-life on 30.04.2021. Co-authored-by: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com>
1 parent 56a4c75 commit d347302

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+798
-792
lines changed

.codesandbox/ci.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"sandboxes": ["github/kentcdodds/react-testing-library-examples"]
2+
"sandboxes": ["github/kentcdodds/react-testing-library-examples"],
3+
"node": "12"
34
}

.github/workflows/validate.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
if: ${{ !contains(github.head_ref, 'all-contributors') }}
1717
strategy:
1818
matrix:
19-
node: [10.14.2, 12, 14, 15, 16]
19+
node: [12, 14, 16]
2020
runs-on: ubuntu-latest
2121
steps:
2222
- name: 🛑 Cancel Previous Runs

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"author": "Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)",
2222
"license": "MIT",
2323
"engines": {
24-
"node": ">=10"
24+
"node": ">=12"
2525
},
2626
"scripts": {
2727
"build": "kcd-scripts build --no-ts-defs --ignore \"**/__tests__/**,**/__node_tests__/**,**/__mocks__/**\" && kcd-scripts build --no-ts-defs --bundle --no-clean",
@@ -46,15 +46,15 @@
4646
"chalk": "^4.1.0",
4747
"dom-accessibility-api": "^0.5.6",
4848
"lz-string": "^1.4.4",
49-
"pretty-format": "^26.6.2"
49+
"pretty-format": "^27.0.2"
5050
},
5151
"devDependencies": {
5252
"@testing-library/jest-dom": "^5.11.6",
5353
"jest-in-case": "^1.0.2",
5454
"jest-serializer-ansi": "^1.0.3",
5555
"jest-watch-select-projects": "^2.0.0",
5656
"jsdom": "^16.4.0",
57-
"kcd-scripts": "^7.5.3",
57+
"kcd-scripts": "^11.0.0",
5858
"typescript": "^4.1.2"
5959
},
6060
"eslintConfig": {
@@ -63,6 +63,7 @@
6363
"plugin:import/typescript"
6464
],
6565
"rules": {
66+
"@typescript-eslint/prefer-includes": "off",
6667
"import/prefer-default-export": "off",
6768
"import/no-unassigned-import": "off",
6869
"import/no-useless-path-segments": "off",

src/DOMElementFilter.ts

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/**
2+
* Source: https://github.com/facebook/jest/blob/e7bb6a1e26ffab90611b2593912df15b69315611/packages/pretty-format/src/plugins/DOMElement.ts
3+
*/
4+
/* eslint-disable -- trying to stay as close to the original as possible */
5+
/* istanbul ignore file */
6+
import type {Config, NewPlugin, Printer, Refs} from 'pretty-format'
7+
8+
function escapeHTML(str: string): string {
9+
return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
10+
}
11+
// Return empty string if keys is empty.
12+
const printProps = (
13+
keys: Array<string>,
14+
props: Record<string, unknown>,
15+
config: Config,
16+
indentation: string,
17+
depth: number,
18+
refs: Refs,
19+
printer: Printer,
20+
): string => {
21+
const indentationNext = indentation + config.indent
22+
const colors = config.colors
23+
return keys
24+
.map(key => {
25+
const value = props[key]
26+
let printed = printer(value, config, indentationNext, depth, refs)
27+
28+
if (typeof value !== 'string') {
29+
if (printed.indexOf('\n') !== -1) {
30+
printed =
31+
config.spacingOuter +
32+
indentationNext +
33+
printed +
34+
config.spacingOuter +
35+
indentation
36+
}
37+
printed = '{' + printed + '}'
38+
}
39+
40+
return (
41+
config.spacingInner +
42+
indentation +
43+
colors.prop.open +
44+
key +
45+
colors.prop.close +
46+
'=' +
47+
colors.value.open +
48+
printed +
49+
colors.value.close
50+
)
51+
})
52+
.join('')
53+
}
54+
55+
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType#node_type_constants
56+
const NodeTypeTextNode = 3
57+
58+
// Return empty string if children is empty.
59+
const printChildren = (
60+
children: Array<unknown>,
61+
config: Config,
62+
indentation: string,
63+
depth: number,
64+
refs: Refs,
65+
printer: Printer,
66+
): string =>
67+
children
68+
.map(child => {
69+
const printedChild =
70+
typeof child === 'string'
71+
? printText(child, config)
72+
: printer(child, config, indentation, depth, refs)
73+
74+
if (
75+
printedChild === '' &&
76+
typeof child === 'object' &&
77+
child !== null &&
78+
(child as Node).nodeType !== NodeTypeTextNode
79+
) {
80+
// A plugin serialized this Node to '' meaning we should ignore it.
81+
return ''
82+
}
83+
return config.spacingOuter + indentation + printedChild
84+
})
85+
.join('')
86+
87+
const printText = (text: string, config: Config): string => {
88+
const contentColor = config.colors.content
89+
return contentColor.open + escapeHTML(text) + contentColor.close
90+
}
91+
92+
const printComment = (comment: string, config: Config): string => {
93+
const commentColor = config.colors.comment
94+
return (
95+
commentColor.open +
96+
'<!--' +
97+
escapeHTML(comment) +
98+
'-->' +
99+
commentColor.close
100+
)
101+
}
102+
103+
// Separate the functions to format props, children, and element,
104+
// so a plugin could override a particular function, if needed.
105+
// Too bad, so sad: the traditional (but unnecessary) space
106+
// in a self-closing tagColor requires a second test of printedProps.
107+
const printElement = (
108+
type: string,
109+
printedProps: string,
110+
printedChildren: string,
111+
config: Config,
112+
indentation: string,
113+
): string => {
114+
const tagColor = config.colors.tag
115+
return (
116+
tagColor.open +
117+
'<' +
118+
type +
119+
(printedProps &&
120+
tagColor.close +
121+
printedProps +
122+
config.spacingOuter +
123+
indentation +
124+
tagColor.open) +
125+
(printedChildren
126+
? '>' +
127+
tagColor.close +
128+
printedChildren +
129+
config.spacingOuter +
130+
indentation +
131+
tagColor.open +
132+
'</' +
133+
type
134+
: (printedProps && !config.min ? '' : ' ') + '/') +
135+
'>' +
136+
tagColor.close
137+
)
138+
}
139+
140+
const printElementAsLeaf = (type: string, config: Config): string => {
141+
const tagColor = config.colors.tag
142+
return (
143+
tagColor.open +
144+
'<' +
145+
type +
146+
tagColor.close +
147+
' …' +
148+
tagColor.open +
149+
' />' +
150+
tagColor.close
151+
)
152+
}
153+
154+
const ELEMENT_NODE = 1
155+
const TEXT_NODE = 3
156+
const COMMENT_NODE = 8
157+
const FRAGMENT_NODE = 11
158+
159+
const ELEMENT_REGEXP = /^((HTML|SVG)\w*)?Element$/
160+
161+
const testNode = (val: any) => {
162+
const constructorName = val.constructor.name
163+
const {nodeType, tagName} = val
164+
const isCustomElement =
165+
(typeof tagName === 'string' && tagName.includes('-')) ||
166+
(typeof val.hasAttribute === 'function' && val.hasAttribute('is'))
167+
168+
return (
169+
(nodeType === ELEMENT_NODE &&
170+
(ELEMENT_REGEXP.test(constructorName) || isCustomElement)) ||
171+
(nodeType === TEXT_NODE && constructorName === 'Text') ||
172+
(nodeType === COMMENT_NODE && constructorName === 'Comment') ||
173+
(nodeType === FRAGMENT_NODE && constructorName === 'DocumentFragment')
174+
)
175+
}
176+
177+
export const test: NewPlugin['test'] = (val: any) =>
178+
val?.constructor?.name && testNode(val)
179+
180+
type HandledType = Element | Text | Comment | DocumentFragment
181+
182+
function nodeIsText(node: HandledType): node is Text {
183+
return node.nodeType === TEXT_NODE
184+
}
185+
186+
function nodeIsComment(node: HandledType): node is Comment {
187+
return node.nodeType === COMMENT_NODE
188+
}
189+
190+
function nodeIsFragment(node: HandledType): node is DocumentFragment {
191+
return node.nodeType === FRAGMENT_NODE
192+
}
193+
194+
export default function createDOMElementFilter(
195+
filterNode: (node: Node) => boolean,
196+
): NewPlugin {
197+
return {
198+
test: (val: any) => val?.constructor?.name && testNode(val),
199+
serialize: (
200+
node: HandledType,
201+
config: Config,
202+
indentation: string,
203+
depth: number,
204+
refs: Refs,
205+
printer: Printer,
206+
) => {
207+
if (nodeIsText(node)) {
208+
return printText(node.data, config)
209+
}
210+
211+
if (nodeIsComment(node)) {
212+
return printComment(node.data, config)
213+
}
214+
215+
const type = nodeIsFragment(node)
216+
? `DocumentFragment`
217+
: node.tagName.toLowerCase()
218+
219+
if (++depth > config.maxDepth) {
220+
return printElementAsLeaf(type, config)
221+
}
222+
223+
return printElement(
224+
type,
225+
printProps(
226+
nodeIsFragment(node)
227+
? []
228+
: Array.from(node.attributes)
229+
.map(attr => attr.name)
230+
.sort(),
231+
nodeIsFragment(node)
232+
? {}
233+
: Array.from(node.attributes).reduce<Record<string, string>>(
234+
(props, attribute) => {
235+
props[attribute.name] = attribute.value
236+
return props
237+
},
238+
{},
239+
),
240+
config,
241+
indentation + config.indent,
242+
depth,
243+
refs,
244+
printer,
245+
),
246+
printChildren(
247+
Array.prototype.slice
248+
.call(node.childNodes || node.children)
249+
.filter(filterNode),
250+
config,
251+
indentation + config.indent,
252+
depth,
253+
refs,
254+
printer,
255+
),
256+
config,
257+
indentation,
258+
)
259+
},
260+
}
261+
}

src/__node_tests__/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ test('works without a browser context on a dom node (JSDOM Fragment)', () => {
6666

6767
expect(dtl.getByLabelText(container, /username/i)).toMatchInlineSnapshot(`
6868
<input
69-
id="username"
69+
id=username
7070
/>
7171
`)
7272
expect(dtl.getByLabelText(container, /password/i)).toMatchInlineSnapshot(`
7373
<input
74-
id="password"
75-
type="password"
74+
id=password
75+
type=password
7676
/>
7777
`)
7878
})

src/__node_tests__/screen.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ test('the screen export throws a helpful error message when no global document i
44
expect(() =>
55
screen.getByText(/hello world/i),
66
).toThrowErrorMatchingInlineSnapshot(
7-
`"For queries bound to document.body a global document has to be available... Learn more: https://testing-library.com/s/screen-global-error"`,
7+
`For queries bound to document.body a global document has to be available... Learn more: https://testing-library.com/s/screen-global-error`,
88
)
99
})
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`getByLabelText query will throw the custom error returned by config.getElementError 1`] = `"My custom error: Unable to find a label with the text of: TEST QUERY"`;
3+
exports[`getByLabelText query will throw the custom error returned by config.getElementError 1`] = `My custom error: Unable to find a label with the text of: TEST QUERY`;
44

5-
exports[`getByText query will throw the custom error returned by config.getElementError 1`] = `"My custom error: Unable to find an element with the text: TEST QUERY. 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."`;
5+
exports[`getByText query will throw the custom error returned by config.getElementError 1`] = `My custom error: Unable to find an element with the text: TEST QUERY. 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.`;

0 commit comments

Comments
 (0)