Skip to content

Commit

Permalink
chore: Release v8 as stable (#979)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
eps1lon and timdeschryver committed Jun 23, 2021
1 parent 56a4c75 commit d347302
Show file tree
Hide file tree
Showing 46 changed files with 798 additions and 792 deletions.
3 changes: 2 additions & 1 deletion .codesandbox/ci.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"sandboxes": ["github/kentcdodds/react-testing-library-examples"]
"sandboxes": ["github/kentcdodds/react-testing-library-examples"],
"node": "12"
}
2 changes: 1 addition & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
if: ${{ !contains(github.head_ref, 'all-contributors') }}
strategy:
matrix:
node: [10.14.2, 12, 14, 15, 16]
node: [12, 14, 16]
runs-on: ubuntu-latest
steps:
- name: 🛑 Cancel Previous Runs
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"author": "Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)",
"license": "MIT",
"engines": {
"node": ">=10"
"node": ">=12"
},
"scripts": {
"build": "kcd-scripts build --no-ts-defs --ignore \"**/__tests__/**,**/__node_tests__/**,**/__mocks__/**\" && kcd-scripts build --no-ts-defs --bundle --no-clean",
Expand All @@ -46,15 +46,15 @@
"chalk": "^4.1.0",
"dom-accessibility-api": "^0.5.6",
"lz-string": "^1.4.4",
"pretty-format": "^26.6.2"
"pretty-format": "^27.0.2"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.11.6",
"jest-in-case": "^1.0.2",
"jest-serializer-ansi": "^1.0.3",
"jest-watch-select-projects": "^2.0.0",
"jsdom": "^16.4.0",
"kcd-scripts": "^7.5.3",
"kcd-scripts": "^11.0.0",
"typescript": "^4.1.2"
},
"eslintConfig": {
Expand All @@ -63,6 +63,7 @@
"plugin:import/typescript"
],
"rules": {
"@typescript-eslint/prefer-includes": "off",
"import/prefer-default-export": "off",
"import/no-unassigned-import": "off",
"import/no-useless-path-segments": "off",
Expand Down
261 changes: 261 additions & 0 deletions src/DOMElementFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
/**
* Source: https://github.com/facebook/jest/blob/e7bb6a1e26ffab90611b2593912df15b69315611/packages/pretty-format/src/plugins/DOMElement.ts
*/
/* eslint-disable -- trying to stay as close to the original as possible */
/* istanbul ignore file */
import type {Config, NewPlugin, Printer, Refs} from 'pretty-format'

function escapeHTML(str: string): string {
return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
}
// Return empty string if keys is empty.
const printProps = (
keys: Array<string>,
props: Record<string, unknown>,
config: Config,
indentation: string,
depth: number,
refs: Refs,
printer: Printer,
): string => {
const indentationNext = indentation + config.indent
const colors = config.colors
return keys
.map(key => {
const value = props[key]
let printed = printer(value, config, indentationNext, depth, refs)

if (typeof value !== 'string') {
if (printed.indexOf('\n') !== -1) {
printed =
config.spacingOuter +
indentationNext +
printed +
config.spacingOuter +
indentation
}
printed = '{' + printed + '}'
}

return (
config.spacingInner +
indentation +
colors.prop.open +
key +
colors.prop.close +
'=' +
colors.value.open +
printed +
colors.value.close
)
})
.join('')
}

// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType#node_type_constants
const NodeTypeTextNode = 3

// Return empty string if children is empty.
const printChildren = (
children: Array<unknown>,
config: Config,
indentation: string,
depth: number,
refs: Refs,
printer: Printer,
): string =>
children
.map(child => {
const printedChild =
typeof child === 'string'
? printText(child, config)
: printer(child, config, indentation, depth, refs)

if (
printedChild === '' &&
typeof child === 'object' &&
child !== null &&
(child as Node).nodeType !== NodeTypeTextNode
) {
// A plugin serialized this Node to '' meaning we should ignore it.
return ''
}
return config.spacingOuter + indentation + printedChild
})
.join('')

const printText = (text: string, config: Config): string => {
const contentColor = config.colors.content
return contentColor.open + escapeHTML(text) + contentColor.close
}

const printComment = (comment: string, config: Config): string => {
const commentColor = config.colors.comment
return (
commentColor.open +
'<!--' +
escapeHTML(comment) +
'-->' +
commentColor.close
)
}

// Separate the functions to format props, children, and element,
// so a plugin could override a particular function, if needed.
// Too bad, so sad: the traditional (but unnecessary) space
// in a self-closing tagColor requires a second test of printedProps.
const printElement = (
type: string,
printedProps: string,
printedChildren: string,
config: Config,
indentation: string,
): string => {
const tagColor = config.colors.tag
return (
tagColor.open +
'<' +
type +
(printedProps &&
tagColor.close +
printedProps +
config.spacingOuter +
indentation +
tagColor.open) +
(printedChildren
? '>' +
tagColor.close +
printedChildren +
config.spacingOuter +
indentation +
tagColor.open +
'</' +
type
: (printedProps && !config.min ? '' : ' ') + '/') +
'>' +
tagColor.close
)
}

const printElementAsLeaf = (type: string, config: Config): string => {
const tagColor = config.colors.tag
return (
tagColor.open +
'<' +
type +
tagColor.close +
' …' +
tagColor.open +
' />' +
tagColor.close
)
}

const ELEMENT_NODE = 1
const TEXT_NODE = 3
const COMMENT_NODE = 8
const FRAGMENT_NODE = 11

const ELEMENT_REGEXP = /^((HTML|SVG)\w*)?Element$/

const testNode = (val: any) => {
const constructorName = val.constructor.name
const {nodeType, tagName} = val
const isCustomElement =
(typeof tagName === 'string' && tagName.includes('-')) ||
(typeof val.hasAttribute === 'function' && val.hasAttribute('is'))

return (
(nodeType === ELEMENT_NODE &&
(ELEMENT_REGEXP.test(constructorName) || isCustomElement)) ||
(nodeType === TEXT_NODE && constructorName === 'Text') ||
(nodeType === COMMENT_NODE && constructorName === 'Comment') ||
(nodeType === FRAGMENT_NODE && constructorName === 'DocumentFragment')
)
}

export const test: NewPlugin['test'] = (val: any) =>
val?.constructor?.name && testNode(val)

type HandledType = Element | Text | Comment | DocumentFragment

function nodeIsText(node: HandledType): node is Text {
return node.nodeType === TEXT_NODE
}

function nodeIsComment(node: HandledType): node is Comment {
return node.nodeType === COMMENT_NODE
}

function nodeIsFragment(node: HandledType): node is DocumentFragment {
return node.nodeType === FRAGMENT_NODE
}

export default function createDOMElementFilter(
filterNode: (node: Node) => boolean,
): NewPlugin {
return {
test: (val: any) => val?.constructor?.name && testNode(val),
serialize: (
node: HandledType,
config: Config,
indentation: string,
depth: number,
refs: Refs,
printer: Printer,
) => {
if (nodeIsText(node)) {
return printText(node.data, config)
}

if (nodeIsComment(node)) {
return printComment(node.data, config)
}

const type = nodeIsFragment(node)
? `DocumentFragment`
: node.tagName.toLowerCase()

if (++depth > config.maxDepth) {
return printElementAsLeaf(type, config)
}

return printElement(
type,
printProps(
nodeIsFragment(node)
? []
: Array.from(node.attributes)
.map(attr => attr.name)
.sort(),
nodeIsFragment(node)
? {}
: Array.from(node.attributes).reduce<Record<string, string>>(
(props, attribute) => {
props[attribute.name] = attribute.value
return props
},
{},
),
config,
indentation + config.indent,
depth,
refs,
printer,
),
printChildren(
Array.prototype.slice
.call(node.childNodes || node.children)
.filter(filterNode),
config,
indentation + config.indent,
depth,
refs,
printer,
),
config,
indentation,
)
},
}
}
6 changes: 3 additions & 3 deletions src/__node_tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ test('works without a browser context on a dom node (JSDOM Fragment)', () => {

expect(dtl.getByLabelText(container, /username/i)).toMatchInlineSnapshot(`
<input
id="username"
id=username
/>
`)
expect(dtl.getByLabelText(container, /password/i)).toMatchInlineSnapshot(`
<input
id="password"
type="password"
id=password
type=password
/>
`)
})
Expand Down
2 changes: 1 addition & 1 deletion src/__node_tests__/screen.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ test('the screen export throws a helpful error message when no global document i
expect(() =>
screen.getByText(/hello world/i),
).toThrowErrorMatchingInlineSnapshot(
`"For queries bound to document.body a global document has to be available... Learn more: https://testing-library.com/s/screen-global-error"`,
`For queries bound to document.body a global document has to be available... Learn more: https://testing-library.com/s/screen-global-error`,
)
})
4 changes: 2 additions & 2 deletions src/__tests__/__snapshots__/get-by-errors.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

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"`;
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`;

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."`;
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.`;
Loading

0 comments on commit d347302

Please sign in to comment.