diff --git a/README.md b/README.md
index 1eb980a1..a8c85483 100644
--- a/README.md
+++ b/README.md
@@ -45,8 +45,8 @@ to maintain.
- [Installation](#installation)
- [Usage](#usage)
- [Custom matchers](#custom-matchers)
- - [`toBeInTheDOM`](#tobeinthedom)
- [`toBeEmpty`](#tobeempty)
+ - [`toBeInTheDocument`](#tobeinthedocument)
- [`toContainElement`](#tocontainelement)
- [`toHaveTextContent`](#tohavetextcontent)
- [`toHaveAttribute`](#tohaveattribute)
@@ -55,6 +55,8 @@ to maintain.
- [`toHaveFocus`](#tohavefocus)
- [`toBeVisible`](#tobevisible)
- [`toBeDisabled`](#tobedisabled)
+- [Deprecated matchers](#deprecated-matchers)
+ - [`toBeInTheDOM`](#tobeinthedom)
- [Inspiration](#inspiration)
- [Other Solutions](#other-solutions)
- [Guiding Principles](#guiding-principles)
@@ -87,9 +89,9 @@ Alternatively, you can selectively import only the matchers you intend to use,
and extend jest's `expect` yourself:
```javascript
-import {toBeInTheDOM, toHaveClass} from 'jest-dom'
+import {toBeInTheDocument, toHaveClass} from 'jest-dom'
-expect.extend({toBeInTheDOM, toHaveClass})
+expect.extend({toBeInTheDocument, toHaveClass})
```
> Note: when using TypeScript, this way of importing matchers won't provide the
@@ -97,67 +99,52 @@ expect.extend({toBeInTheDOM, toHaveClass})
## Custom matchers
-### `toBeInTheDOM`
+### `toBeEmpty`
```typescript
-toBeInTheDOM(container?: HTMLElement | SVGElement)
-```
-
-This allows you to assert whether an element present in the DOM container or not. If no DOM container is specified it will use the default DOM context.
-
-#### Using the default DOM container
-
-```javascript
-// add the custom expect matchers once
-import 'jest-dom/extend-expect'
-
-// ...
-// 2
-expect(queryByTestId(container, 'count-value')).toBeInTheDOM()
-expect(queryByTestId(container, 'count-value1')).not.toBeInTheDOM()
-// ...
+toBeEmpty()
```
-#### Using a specified DOM container
+This allows you to assert whether an element has content or not.
```javascript
// add the custom expect matchers once
import 'jest-dom/extend-expect'
// ...
-//
-expect(queryByTestId(container, 'descendant')).toBeInTheDOM(
- queryByTestId(container, 'ancestor'),
-)
-expect(queryByTestId(container, 'ancestor')).not.toBeInTheDOM(
- queryByTestId(container, 'descendant'),
-)
+//
+expect(queryByTestId(container, 'empty')).toBeEmpty()
+expect(queryByTestId(container, 'not-empty')).not.toBeEmpty()
// ...
```
-> Note: when using `toBeInTheDOM`, make sure you use a query function
-> (like `queryByTestId`) rather than a get function (like `getByTestId`).
-> Otherwise the `get*` function could throw an error before your assertion.
-
-### `toBeEmpty`
+### `toBeInTheDocument`
```typescript
-toBeEmpty()
+toBeInTheDocument()
```
-This allows you to assert whether an element has content or not.
+This allows you to assert whether an element is present in the document or not.
```javascript
// add the custom expect matchers once
import 'jest-dom/extend-expect'
// ...
-//
-expect(queryByTestId(container, 'empty')).toBeEmpty()
-expect(queryByTestId(container, 'not-empty')).not.toBeEmpty()
+// document.body.innerHTML = `Html Element`
+
+// const htmlElement = document.querySelector('[data-testid="html-element"]')
+// const svgElement = document.querySelector('[data-testid="html-element"]')
+// const detachedElement = document.createElement('div')
+
+expect(htmlElement).toBeInTheDocument()
+expect(svgElement).toBeInTheDocument()
+expect(detacthedElement).not.toBeInTheDocument()
// ...
```
+> Note: This will not find detached elements. The element must be added to the document to be found. If you desire to search in a detached element please use: [`toContainElement`](#tocontainelement)
+
### `toContainElement`
```typescript
@@ -389,6 +376,16 @@ expect(getByText(container, 'LINK')).not.toBeDisabled()
// ...
```
+## Deprecated matchers
+
+### `toBeInTheDOM`
+
+> Note: The differences between `toBeInTheDOM` and `toBeInTheDocument` are significant. Replacing all uses of `toBeInTheDOM` with `toBeInTheDocument` will likely cause unintended consequences in your tests. Please make sure when replacing `toBeInTheDOM` to read through the replacements below to see which use case works better for your needs.
+
+> Please use [`toContainElement`](#tocontainelement) for searching a specific container.
+
+> Please use [`toBeInTheDocument`](#tobeinthedocument) for searching the entire document.
+
## Inspiration
This whole library was extracted out of Kent C. Dodds' [dom-testing-library][],
diff --git a/extend-expect.d.ts b/extend-expect.d.ts
index 5a059573..6f361223 100644
--- a/extend-expect.d.ts
+++ b/extend-expect.d.ts
@@ -1,6 +1,10 @@
declare namespace jest {
interface Matchers {
+ /**
+ * @deprecated
+ */
toBeInTheDOM(container?: HTMLElement | SVGElement): R
+ toBeInTheDocument(): R
toBeVisible(): R
toBeEmpty(): R
toBeDisabled(): R
diff --git a/src/__tests__/to-be-in-the-document.js b/src/__tests__/to-be-in-the-document.js
new file mode 100644
index 00000000..c8ebd0e6
--- /dev/null
+++ b/src/__tests__/to-be-in-the-document.js
@@ -0,0 +1,24 @@
+test('.toBeInTheDocument', () => {
+ document.body.innerHTML = `
+ Html Element
+ `
+
+ const htmlElement = document.querySelector('[data-testid="html-element"]')
+ const svgElement = document.querySelector('[data-testid="html-element"]')
+ const detachedElement = document.createElement('div')
+ const fakeElement = {thisIsNot: 'an html element'}
+ const undefinedElement = undefined
+ const nullElement = null
+
+ expect(htmlElement).toBeInTheDocument()
+ expect(svgElement).toBeInTheDocument()
+ expect(detachedElement).not.toBeInTheDocument()
+
+ // negative test cases wrapped in throwError assertions for coverage.
+ expect(() => expect(htmlElement).not.toBeInTheDocument()).toThrowError()
+ expect(() => expect(svgElement).not.toBeInTheDocument()).toThrowError()
+ expect(() => expect(detachedElement).toBeInTheDocument()).toThrowError()
+ expect(() => expect(fakeElement).toBeInTheDocument()).toThrowError()
+ expect(() => expect(undefinedElement).toBeInTheDocument()).toThrowError()
+ expect(() => expect(nullElement).toBeInTheDocument()).toThrowError()
+})
diff --git a/src/__tests__/to-be-in-the-dom.js b/src/__tests__/to-be-in-the-dom.js
index 465baca6..4ef9fb48 100644
--- a/src/__tests__/to-be-in-the-dom.js
+++ b/src/__tests__/to-be-in-the-dom.js
@@ -1,6 +1,9 @@
import {render} from './helpers/test-utils'
test('.toBeInTheDOM', () => {
+ // @deprecated intentionally hiding warnings for test clarity
+ const spy = jest.spyOn(console, 'warn').mockImplementation(() => {})
+
const {queryByTestId} = render(`
@@ -51,4 +54,6 @@ test('.toBeInTheDOM', () => {
expect(() => {
expect(valueElement).toBeInTheDOM(fakeElement)
}).toThrowError()
+
+ spy.mockRestore()
})
diff --git a/src/__tests__/utils.js b/src/__tests__/utils.js
new file mode 100644
index 00000000..1b24a704
--- /dev/null
+++ b/src/__tests__/utils.js
@@ -0,0 +1,43 @@
+import {checkDocumentKey, deprecate} from '../utils'
+
+function matcherMock() {}
+
+test('deprecate', () => {
+ const spy = jest.spyOn(console, 'warn').mockImplementation(() => {})
+ const name = 'test'
+ const replacement = 'test'
+ const message = `Warning: ${name} has been deprecated and will be removed in future updates.`
+
+ deprecate(name, replacement)
+ expect(spy).toHaveBeenCalledWith(message, replacement)
+
+ deprecate(name)
+ expect(spy).toHaveBeenCalledWith(message, undefined)
+
+ spy.mockRestore()
+})
+
+test('checkDocumentKey', () => {
+ const fakeKey = 'fakeKey'
+ const realKey = 'documentElement'
+ const badKeyMessage = `${fakeKey} is undefined on document but is required to use ${
+ matcherMock.name
+ }.`
+
+ const badDocumentMessage = `document is undefined on global but is required to use ${
+ matcherMock.name
+ }.`
+
+ expect(() =>
+ checkDocumentKey(document, realKey, matcherMock),
+ ).not.toThrowError()
+
+ expect(() => {
+ checkDocumentKey(document, fakeKey, matcherMock)
+ }).toThrow(badKeyMessage)
+
+ expect(() => {
+ //eslint-disable-next-line no-undef
+ checkDocumentKey(undefined, realKey, matcherMock)
+ }).toThrow(badDocumentMessage)
+})
diff --git a/src/index.js b/src/index.js
index 7f3b472e..a39b83ac 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,5 @@
import {toBeInTheDOM} from './to-be-in-the-dom'
+import {toBeInTheDocument} from './to-be-in-the-document'
import {toBeEmpty} from './to-be-empty'
import {toContainElement} from './to-contain-element'
import {toHaveTextContent} from './to-have-text-content'
@@ -11,6 +12,7 @@ import {toBeDisabled} from './to-be-disabled'
export {
toBeInTheDOM,
+ toBeInTheDocument,
toBeEmpty,
toContainElement,
toHaveTextContent,
diff --git a/src/to-be-in-the-document.js b/src/to-be-in-the-document.js
new file mode 100644
index 00000000..9eb0487d
--- /dev/null
+++ b/src/to-be-in-the-document.js
@@ -0,0 +1,25 @@
+import {matcherHint, printReceived} from 'jest-matcher-utils'
+import {checkHtmlElement, checkDocumentKey} from './utils'
+
+export function toBeInTheDocument(element) {
+ checkHtmlElement(element, toBeInTheDocument, this)
+ checkDocumentKey(global.document, 'documentElement', toBeInTheDocument)
+
+ return {
+ pass: document.documentElement.contains(element),
+ message: () => {
+ return [
+ matcherHint(
+ `${this.isNot ? '.not' : ''}.toBeInTheDocument`,
+ 'element',
+ '',
+ ),
+ '',
+ 'Received:',
+ ` ${printReceived(
+ element.hasChildNodes() ? element.cloneNode(false) : element,
+ )}`,
+ ].join('\n')
+ },
+ }
+}
diff --git a/src/to-be-in-the-dom.js b/src/to-be-in-the-dom.js
index b413557b..c5c49edd 100644
--- a/src/to-be-in-the-dom.js
+++ b/src/to-be-in-the-dom.js
@@ -1,7 +1,12 @@
import {matcherHint, printReceived} from 'jest-matcher-utils'
-import {checkHtmlElement} from './utils'
+import {checkHtmlElement, deprecate} from './utils'
export function toBeInTheDOM(element, container) {
+ deprecate(
+ 'toBeInTheDOM',
+ 'Please use toBeInTheDocument for searching the entire document and toContainElement for searching a specific container.',
+ )
+
if (element) {
checkHtmlElement(element, toBeInTheDOM, this)
}
diff --git a/src/utils.js b/src/utils.js
index 7540bdcb..2bd89492 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -40,6 +40,39 @@ function checkHtmlElement(htmlElement, ...args) {
}
}
+class InvalidDocumentError extends Error {
+ constructor(message, matcherFn) {
+ super()
+
+ /* istanbul ignore next */
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, matcherFn)
+ }
+
+ this.message = message
+ }
+}
+
+function checkDocumentKey(document, key, matcherFn) {
+ if (typeof document === 'undefined') {
+ throw new InvalidDocumentError(
+ `document is undefined on global but is required to use ${
+ matcherFn.name
+ }.`,
+ matcherFn,
+ )
+ }
+
+ if (typeof document[key] === 'undefined') {
+ throw new InvalidDocumentError(
+ `${key} is undefined on document but is required to use ${
+ matcherFn.name
+ }.`,
+ matcherFn,
+ )
+ }
+}
+
function display(value) {
return typeof value === 'string' ? value : stringify(value)
}
@@ -66,4 +99,13 @@ function matches(textToMatch, node, matcher) {
}
}
-export {checkHtmlElement, getMessage, matches}
+function deprecate(name, replacementText) {
+ // Notify user that they are using deprecated functionality.
+ // eslint-disable-next-line no-console
+ console.warn(
+ `Warning: ${name} has been deprecated and will be removed in future updates.`,
+ replacementText,
+ )
+}
+
+export {checkDocumentKey, checkHtmlElement, deprecate, getMessage, matches}