diff --git a/README.md b/README.md
index e93abdb2..98643c2b 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,8 @@ clear to read and to maintain.
- [`toHaveErrorMessage`](#tohaveerrormessage)
- [`toBePressed`](#tobepressed)
- [`toBePartiallyPressed`](#tobepartiallypressed)
+ - [`toAppearBefore`](#toappearbefore)
+ - [`toAppearAfter`](#toappearafter)
- [Deprecated matchers](#deprecated-matchers)
- [`toBeEmpty`](#tobeempty)
- [`toBeInTheDOM`](#tobeinthedom)
@@ -1386,6 +1388,72 @@ screen
.toBePartiallyPressed()
```
+
+
+### `toAppearBefore`
+
+This checks if a given element appears before another element in the DOM tree,
+as per
+[`compareDocumentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
+
+```typescript
+toAppearBefore()
+```
+
+#### Examples
+
+```html
+
+ Text A
+ Text B
+
+```
+
+```javascript
+const textA = queryByTestId('text-a')
+const textB = queryByTestId('text-b')
+
+expect(textA).toAppearBefore(textB)
+expect(textB).not.toAppearBefore(textA)
+```
+
+> Note: This matcher does not take into account CSS styles that may modify the
+> display order of elements, eg:
+>
+> - `flex-direction: row-reverse`,
+> - `flex-direction: column-reverse`,
+> - `display: grid`
+
+### `toAppearAfter`
+
+This checks if a given element appears after another element in the DOM tree, as
+per
+[`compareDocumentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
+
+```typescript
+toAppearAfter()
+```
+
+#### Examples
+
+```html
+
+ Text A
+ Text B
+
+```
+
+```javascript
+const textA = queryByTestId('text-a')
+const textB = queryByTestId('text-b')
+
+expect(textB).toAppearAfter(textA)
+expect(textA).not.toAppearAfter(textB)
+```
+
+> Note: This matcher does not take into account CSS styles that may modify the
+> display order of elements, see [`toAppearBefore()`](#toappearbefore)
+
## Deprecated matchers
### `toBeEmpty`
diff --git a/src/__tests__/to-appear-before.js b/src/__tests__/to-appear-before.js
new file mode 100644
index 00000000..c6af15e0
--- /dev/null
+++ b/src/__tests__/to-appear-before.js
@@ -0,0 +1,105 @@
+import {render} from './helpers/test-utils'
+
+describe('.toAppearBefore', () => {
+ const {queryByTestId} = render(`
+
+ `)
+
+ const textA = queryByTestId('text-a')
+ const textB = queryByTestId('text-b')
+ const divA = queryByTestId('div-a')
+
+ it('returns correct result when first element is before second element', () => {
+ expect(textA).toAppearBefore(textB)
+ })
+
+ it('returns correct for .not() invocation', () => {
+ expect(textB).not.toAppearBefore(textA)
+ })
+
+ it('errors out when first element is not before second element', () => {
+ expect(() => expect(textB).toAppearBefore(textA)).toThrowError(
+ /Received: Node.DOCUMENT_POSITION_PRECEDING \(2\)/i,
+ )
+ })
+
+ it('errors out when .not is used but first element is actually before second element', () => {
+ expect(() => expect(textA).not.toAppearBefore(textB)).toThrowError(
+ /\.not\.toAppearBefore/i,
+ )
+ })
+
+ it('errors out when first element is parent of second element', () => {
+ expect(() => expect(divA).toAppearBefore(textA)).toThrowError(
+ /Received: Unknown document position \(20\)/i,
+ )
+ })
+
+ it('errors out when first element is child of second element', () => {
+ expect(() => expect(textA).toAppearBefore(divA)).toThrowError(
+ /Received: Unknown document position \(10\)/i,
+ )
+ })
+
+ it('errors out when either first or second element is not HTMLElement', () => {
+ expect(() => expect(1).toAppearBefore(textB)).toThrowError()
+ expect(() => expect(textA).toAppearBefore(1)).toThrowError()
+ })
+})
+
+describe('.toAppearAfter', () => {
+ const {queryByTestId} = render(`
+
+ `)
+
+ const textA = queryByTestId('text-a')
+ const textB = queryByTestId('text-b')
+ const divA = queryByTestId('div-a')
+
+ it('returns correct result when first element is after second element', () => {
+ expect(textB).toAppearAfter(textA)
+ })
+
+ it('returns correct for .not() invocation', () => {
+ expect(textA).not.toAppearAfter(textB)
+ })
+
+ it('errors out when first element is not after second element', () => {
+ expect(() => expect(textA).toAppearAfter(textB)).toThrowError(
+ /Received: Node.DOCUMENT_POSITION_FOLLOWING \(4\)/i,
+ )
+ })
+
+ it('errors out when .not is used but first element is actually after second element', () => {
+ expect(() => expect(textB).not.toAppearAfter(textA)).toThrowError(
+ /\.not\.toAppearAfter/i,
+ )
+ })
+
+ it('errors out when first element is parent of second element', () => {
+ expect(() => expect(divA).toAppearAfter(textA)).toThrowError(
+ /Received: Unknown document position \(20\)/i,
+ )
+ })
+
+ it('errors out when first element is child of second element', () => {
+ expect(() => expect(textA).toAppearAfter(divA)).toThrowError(
+ /Received: Unknown document position \(10\)/i,
+ )
+ })
+
+ it('errors out when either first or second element is not HTMLElement', () => {
+ expect(() => expect(1).toAppearAfter(textB)).toThrowError()
+ expect(() => expect(textA).toAppearAfter(1)).toThrowError()
+ })
+})
diff --git a/src/matchers.js b/src/matchers.js
index e121edb8..f67054f4 100644
--- a/src/matchers.js
+++ b/src/matchers.js
@@ -27,3 +27,4 @@ export {toHaveErrorMessage} from './to-have-errormessage'
export {toHaveSelection} from './to-have-selection'
export {toBePressed} from './to-be-pressed'
export {toBePartiallyPressed} from './to-be-partially-pressed'
+export {toAppearBefore, toAppearAfter} from './to-appear-before'
diff --git a/src/to-appear-before.js b/src/to-appear-before.js
new file mode 100644
index 00000000..66ccd212
--- /dev/null
+++ b/src/to-appear-before.js
@@ -0,0 +1,60 @@
+import {checkHtmlElement} from './utils'
+
+// ref: https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
+const DOCUMENT_POSITIONS_STRINGS = {
+ [Node.DOCUMENT_POSITION_DISCONNECTED]: 'Node.DOCUMENT_POSITION_DISCONNECTED',
+ [Node.DOCUMENT_POSITION_PRECEDING]: 'Node.DOCUMENT_POSITION_PRECEDING',
+ [Node.DOCUMENT_POSITION_FOLLOWING]: 'Node.DOCUMENT_POSITION_FOLLOWING',
+ [Node.DOCUMENT_POSITION_CONTAINS]: 'Node.DOCUMENT_POSITION_CONTAINS',
+ [Node.DOCUMENT_POSITION_CONTAINED_BY]: 'Node.DOCUMENT_POSITION_CONTAINED_BY',
+ [Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC]:
+ 'Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC',
+}
+
+function makeDocumentPositionErrorString(documentPosition) {
+ if (documentPosition in DOCUMENT_POSITIONS_STRINGS) {
+ return `${DOCUMENT_POSITIONS_STRINGS[documentPosition]} (${documentPosition})`
+ }
+
+ return `Unknown document position (${documentPosition})`
+}
+
+function checkToAppear(methodName, targetDocumentPosition) {
+ // eslint-disable-next-line func-names
+ return function (element, secondElement) {
+ checkHtmlElement(element, toAppearBefore, this)
+ checkHtmlElement(secondElement, toAppearBefore, this)
+
+ const documentPosition = element.compareDocumentPosition(secondElement)
+ const pass = documentPosition === targetDocumentPosition
+
+ return {
+ pass,
+ message: () => {
+ return [
+ this.utils.matcherHint(
+ `${this.isNot ? '.not' : ''}.${methodName}`,
+ 'element',
+ 'secondElement',
+ ),
+ '',
+ `Received: ${makeDocumentPositionErrorString(documentPosition)}`,
+ ].join('\n')
+ },
+ }
+ }
+}
+
+export function toAppearBefore(element, secondElement) {
+ return checkToAppear(
+ 'toAppearBefore',
+ Node.DOCUMENT_POSITION_FOLLOWING,
+ ).apply(this, [element, secondElement])
+}
+
+export function toAppearAfter(element, secondElement) {
+ return checkToAppear('toAppearAfter', Node.DOCUMENT_POSITION_PRECEDING).apply(
+ this,
+ [element, secondElement],
+ )
+}
diff --git a/types/matchers.d.ts b/types/matchers.d.ts
index 34fdaff8..82de01dc 100755
--- a/types/matchers.d.ts
+++ b/types/matchers.d.ts
@@ -810,6 +810,46 @@ declare namespace matchers {
* [testing-library/jest-dom#tobepartiallypressed](https://github.com/testing-library/jest-dom#tobepartiallypressed)
*/
toBePartiallyPressed(): R
+ /*
+ * @description
+ * This checks if a given element appears before another element in the DOM tree, as per [`compareDocumentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
+ *
+ * @example
+ *
+ * Text A
+ * Text B
+ *
+ *
+ * const textA = queryByTestId('text-a')
+ * const textB = queryByTestId('text-b')
+ *
+ * expect(textA).toAppearBefore(textB)
+ * expect(textB).not.toAppearBefore(textA)
+ *
+ * @See
+ * [testing-library/jest-dom#toappearbefore](https://github.com/testing-library/jest-dom#toappearbefore)
+ */
+ toAppearBefore(element: HTMLElement | SVGElement): R
+ /*
+ * @description
+ * This checks if a given element appears after another element in the DOM tree, as per [`compareDocumentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
+ *
+ * @example
+ *
+ * Text A
+ * Text B
+ *
+ *
+ * const textA = queryByTestId('text-a')
+ * const textB = queryByTestId('text-b')
+ *
+ * expect(textB).toAppearAfter(textA)
+ * expect(textA).not.toAppearAfter(textB)
+ *
+ * @See
+ * [testing-library/jest-dom#toappearafter](https://github.com/testing-library/jest-dom#toappearafter)
+ */
+ toAppearAfter(element: HTMLElement | SVGElement): R
}
}