Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Release v8 as stable #979

Merged
merged 10 commits into from
Jun 23, 2021
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