Skip to content

Commit 430e952

Browse files
committed
Make screen compatible with document.body.replaceWith()
What: The screen utility no longer caches the body element at import time. When the body or entire HTML document changes with replaceWith(), the screen utility continues to operate on the global value. Why: My integration testing environment strives to creates testing scenarios that are as realistic to production as possible. This aligns with the library's guiding principles: > We try to only expose methods and utilities that encourage you to > write tests that closely resemble how your web pages are used. To assist with this, parts of my integration testing environment involve a 2-step process: 1. Run a command to dump the HTML documents rendered by the backend. 2. Load those dumps into a Jest test environment to assert JavaScript behavior. Loading the HTML document in Jest is a challenge as the global HTML document is setup by jest-environment-jsdom ahead of the test running. To make this work well, the environment "replaces" the jsdom HTML document like: ``` const content = loadHTML(...); const tmpDom = new jsdom.JSDOM(content); const tmpDocument = tmpDom.window.document; // Replace the global document with the one from the backend. document.documentElement.replaceWith(document.adoptNode(tmpDocument.documentElement)); ``` This works out great in practice for me. The JavaScript is tested against real HTML documents rather than fabricated ones for the test. In the event that the backend changes how elements are rendered that, the integration tests will expose this and force a fix to make the two compatible. With this change, the screen utility is now usable as a convenience. How: Rather than caching the document.body at import time, screen now dynamically uses the most up date value on the global document.
1 parent 7917358 commit 430e952

File tree

2 files changed

+25
-16
lines changed

2 files changed

+25
-16
lines changed

src/__tests__/screen.js

+9
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,12 @@ test('exposes debug method', () => {
118118
`)
119119
console.log.mockClear()
120120
})
121+
122+
test('queries after replaceWith', async () => {
123+
const newBody = document.createElement('body')
124+
newBody.innerHTML = '<div>replaceWith element</div>'
125+
document.body.replaceWith(newBody)
126+
screen.getByText('replaceWith element')
127+
await screen.findByText('replaceWith element')
128+
expect(screen.queryByText('replaceWith element')).not.toBeNull()
129+
})

src/screen.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// TODO: Statically verify we don't rely on NodeJS implicit named imports.
33
import lzString from 'lz-string'
44
import {type OptionsReceived} from 'pretty-format'
5-
import {getQueriesForElement} from './get-queries-for-element'
65
import {getDocument} from './helpers'
76
import {logDOM} from './pretty-dom'
87
import * as queries from './queries'
@@ -46,19 +45,20 @@ const logTestingPlaygroundURL = (element = getDocument().body) => {
4645
return playgroundUrl
4746
}
4847

49-
const initialValue = {debug, logTestingPlaygroundURL}
48+
const initialValue: Record<string, Function> = {debug, logTestingPlaygroundURL}
5049

51-
export const screen =
52-
typeof document !== 'undefined' && document.body // eslint-disable-line @typescript-eslint/no-unnecessary-condition
53-
? getQueriesForElement(document.body, queries, initialValue)
54-
: Object.keys(queries).reduce((helpers, key) => {
55-
// `key` is for all intents and purposes the type of keyof `helpers`, which itself is the type of `initialValue` plus incoming properties from `queries`
56-
// if `Object.keys(something)` returned Array<keyof typeof something> this explicit type assertion would not be necessary
57-
// see https://stackoverflow.com/questions/55012174/why-doesnt-object-keys-return-a-keyof-type-in-typescript
58-
helpers[key as keyof typeof initialValue] = () => {
59-
throw new TypeError(
60-
'For queries bound to document.body a global document has to be available... Learn more: https://testing-library.com/s/screen-global-error',
61-
)
62-
}
63-
return helpers
64-
}, initialValue)
50+
export const screen = Object.entries(queries).reduce((helpers, [key, fn]) => {
51+
// `key` is for all intents and purposes the type of keyof `helpers`, which itself is the type of `initialValue` plus incoming properties from `queries`
52+
// if `Object.keys(something)` returned Array<keyof typeof something> this explicit type assertion would not be necessary
53+
// see https://stackoverflow.com/questions/55012174/why-doesnt-object-keys-return-a-keyof-type-in-typescript
54+
helpers[key] = (...args: any[]) => {
55+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
56+
if (typeof document === 'undefined' || !document.body) {
57+
throw new TypeError(
58+
'For queries bound to document.body a global document has to be available... Learn more: https://testing-library.com/s/screen-global-error',
59+
)
60+
}
61+
return fn(document.body, ...(args as any[]))
62+
}
63+
return helpers
64+
}, initialValue)

0 commit comments

Comments
 (0)