I am confused by when/how a queried element get updated. For example:
I have a button that counts up. And when it counts to 3, it disappears.
import React, { useState } from "react";
export default () => {
const [count, setCount] = useState(0);
return (
<div>
{count < 3 && (
<button onClick={() => setCount(count + 1)}>{count}</button>
)}
</div>
);
};
I'm testing it like so:
test("Counter counts up and disappears", () => {
const { queryByText } = render(<App" />);
const button = queryByText("0");
expect(button.textContent).toEqual("0");
fireEvent.click(button);
expect(button.textContent).toEqual("1");
fireEvent.click(button);
expect(button.textContent).toEqual("2");
fireEvent.click(button);
expect(button).toBe(null);
});
The test failes with the following error:
expect(received).toBe(expected)
Expected value to be (using ===):
null
Received:
<button>2</button>
Why does the button element knows its text content has changed? We didn't need to run queryByText("1")
and queryByText("2")
after each click.
And why it doesn't know that the button has been unmounted after the last click? And we do need to do queryByText("3")
if we want to check that it is indeed null.
Here's the code sandbox: https://codesandbox.io/s/react-testing-library-demo-76zhi
I guess it's about how react-dom
and jsdom
(which used by testing too) works.
(Please correct me or let me know if guessing is unacceptable! I will remove this. )
Button type return by queryByText
is HtmlElment
react
or react-dom
is just minimize the task for dom manipulation, eventually jsdom
would still do the text changing and button removing works.
Since the testing tool is using jsdom
, I check the removeChild
part of code, and believe they are following W3C specification. I assume in the level of dom manipulation, jsdom
would works like browsers. Thus I simulated your code like this:
const btn = document.getElementById('btn');
const btnContainer = document.getElementById('btnContainer');
btn.textContent = '1';
console.log('btn after click 1: ' + btn);
btn.textContent = '2';
console.log('btn after click 2: ' + btn);
btnContainer.removeChild(btn);
console.log('btn after removed: '+ btn);
<div id='btnContainer'><button id='btn'>0<button></div>
div
, the btn
variable is still [object HTMLButtonElement]
Maybe It's similar to use splice
to an array which contained a defined object, the initial obj still remains the same?const obj = { 'a': 1};
const arr = [obj];
arr[0]['a'] = 2;
arr.splice(0, -1);
console.log(obj)
Therefore, I think it's more about how garbage collection works in javascript.
In addition, maybe not to reuse the query result, and always query again in this situation, the result will be more expectable.
P.S. I found there is a method call toBeInTheDocument
!