-
Notifications
You must be signed in to change notification settings - Fork 47k
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
ValidateDOMNesting tests(#11299) #11742
Conversation
@gaearon Could you please take a quick look at the lines marked with TODO? I have added my doubts as comments. Thanks in advance :) |
]; | ||
function expectInvalidNestingWarning(shouldWarn, tags) { | ||
// TODO: I get the error <spyOn> : error has already been spied upon although | ||
// i am resetting the jest modules before every assertion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. So, jest.resetModules()
only resets requires, forcing next require()
to load fresh versions. But it doesn't actually reset global objects like console
.
I think it's fine to just put spyOnDev(console, 'error')
in the beginning of every test case. That's pretty explicit.
expectInvalidNestingWarning(false, ['div', 'ul', 'li', 'dd', 'li']); | ||
|
||
// TODO: Previously used test scenarios which will fail now because | ||
// root element is div. Remove them? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. Let's change the code in expectInvalidNestingWarning
to treat the first item in the array as the root element itself.
For example, for ['div', 'label', 'div']
have it render <label><div /></label>
into a DOM div
.
Then, for ['body', 'datalist', 'option']
it would render <datalist><option /></datalist>
into a body
.
5a76ca2
to
993f736
Compare
@gaearon This PR is ready for your review :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's be more specific about the warnings we check for.
while (tags.length) { | ||
element = React.createElement(tags.pop(), null, element); | ||
} | ||
const errorCount = console.error.calls.count(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call console.error.calls.reset()
here. Then you can assert a specific count later (presumably 1?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see it's not 1 for some tests.
In that case let's pass expectedWarnings
into this function as an argument?
e.g.
expectInvalidNestingWarning(true, ['body', 'body'], [
'validateDOMNesting(...): <body> cannot appear as a child of <body>',
'render(): Rendering components directly into document.body is discouraged'
]);
Then you can assert that we have exactly that number of warnings, and they match the passed ones with toContain()
.
|
||
function expectInvalidNestingWarning(shouldWarn, tags) { | ||
let element = null; | ||
const container = document.createElement(tags.splice(0, 1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's a bit confusing that this function can mutate its argument. Let's copy tags
at the beginning of this function?
const errorCount = console.error.calls.count(); | ||
ReactDOM.render(element, container); | ||
if (shouldWarn) { | ||
expect(console.error.calls.count() > errorCount).toEqual(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related to previous comment: this should assert on a specific number.
We should also assert on a specific message. It's fine if it's not 100% specific: you can assert on a part of the message with toContain()
.
ffb5e71
to
a20d835
Compare
@gaearon Have made the changes. Please take a look :) |
}); | ||
} | ||
}); | ||
|
||
it('allows valid nestings', () => { | ||
if (__DEV__) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s remove DEV check here in tests. We want the code to run in any case. However we want to put the assertions about warnings inside a DEV check since in production they aren’t expected to warn.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gaearon I believe the reason why the DEV checks are right at the beginning of the tests is because ALL the assertions are related to warnings, which will not be testable in prod as we wont be seeing any. So no point in letting any of the code execute in prod.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used the __DEV__
checks at the beginning of the tests because some functions attached to validateDOMNesting
are actually undefined in production so the tests would crash. That's not a problem when tests are written against the public API.
which will not be testable in prod as we wont be seeing any. So no point in letting any of the code execute in prod.
There is still value in executing this code in PROD. In particular, we want to verify that the code doesn't crash. For example, imagine we made a mistake and called validateDOMNesting.updatedAncestorInfo
in production environment. It would throw. The tests should verify that nothing bad happens by stressing that code path.
In this particular case it's true that if there was a big issue like this, other tests would probably fail too. But we still want to have coverage like this. I hope it makes sense!
a20d835
to
fe444f5
Compare
Looks like you need to run |
You can run production tests with:
You'll see that it fails because |
* Rewrite tests using only public API. * Modified the tests to prevent duplication of code. * Code review changes implemented. * Removed the .internal from the test file name as its now written using public APIs.
fe444f5
to
90eda25
Compare
@gaearon Thanks a lot for explaining why letting the code run in prod is useful. It makes a lot of sense!!! I have made the changes. Please take a look :) |
|
||
function expectInvalidNestingWarning(shouldWarn, tagsList, warningsList = []) { | ||
let element = null; | ||
let tags = tagsList; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn’t copy the array, it only creates a reference to it. Therefore the splice
call later mutates it.
It would be clearer IMO if we didn’t mutate the input. Otherwise, for example, declaring an array as a variable in the test and passing it two times wouldn’t work. We don’t do this but it could be potentially confusing in the future.
To solve it we need to copy the array on this line.
Now that we pass warnings, we don't need to pass a boolean.
I added some extra coverage to ensure we include the component stack traces into the warning messages. I needed to convert |
Thanks for doing this! |
I have 2 doubts in this( 1 is an error, other is a clarification). Have marked them as TODO so that its easy to spot.