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

screen incompatible with document.body.replaceWith() #1310

Open
jdufresne opened this issue Apr 28, 2024 · 3 comments · May be fixed by #1311
Open

screen incompatible with document.body.replaceWith() #1310

jdufresne opened this issue Apr 28, 2024 · 3 comments · May be fixed by #1311

Comments

@jdufresne
Copy link
Contributor

  • @testing-library/dom version: main
  • Testing Framework and version: Jest 29.7.0
  • DOM Environment: jsdom 20.0.0

Relevant code or config:

// src/__tests__/screen.js
test('queries after replaceWith', async () => {
  const newBody = document.createElement('body')
  newBody.innerHTML = '<div>replaceWith element</div>'
  document.body.replaceWith(newBody)
  screen.getByText('replaceWith element')
  await screen.findByText('replaceWith element')
  expect(screen.queryByText('replaceWith element')).not.toBeNull()
})

What you did:

Used Element.replaceWith() in a test environment to help build full realistic HTML DOMs to verify application behavior.

https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceWith

What happened:

Received the error:

TestingLibraryElementError: Unable to find an element with the text: replaceWith element. 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.

Reproduction:

// src/__tests__/screen.js
test('queries after replaceWith', async () => {
  const newBody = document.createElement('body')
  newBody.innerHTML = '<div>replaceWith element</div>'
  document.body.replaceWith(newBody)
  screen.getByText('replaceWith element')
  await screen.findByText('replaceWith element')
  expect(screen.queryByText('replaceWith element')).not.toBeNull()
})

Problem description:

The current behavior is a problem for me as it makes screen unusable. The test environment uses replaceWith in an attempt to build a full production accurate HTML document, not just a component fragment within an empty document.

Looking into this a bit, when screen is imported, it caches the current document.body and uses that for the remainder of the test. When using replaceWith that cached element becomes invalid and is no longer the same as the body in the document.

As a workaround, I use getByText(document.body, ...).

Suggested solution:

For this use case to work requires screen to dynamically use the current document.body on each access rather than a cached value. Something like a getter or a function rather than just a value.

Another thought would be to provide some API to reload and recache the screen value.

@MatanBobi
Copy link
Member

MatanBobi commented Apr 28, 2024

I'm hesitating whether this is a valid use case we should be supporting. What are the use cases to actually replace the entire body of the document? This isn't something done as part of frameworks and since there's a workaround to this, I'm not sure we should be providing a solution as part of our code.

@jdufresne
Copy link
Contributor Author

I was able to get this to work in #1311.

But right now the types are failing and I'm uncertain how to resolve them.

I'll copy the rationale here which I feel better explains my use case:


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.

@eps1lon eps1lon linked a pull request Apr 28, 2024 that will close this issue
4 tasks
@bshifrin
Copy link

@jdufresne, I have the same use case and deal with the same limitation of the screen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants