-
Notifications
You must be signed in to change notification settings - Fork 47.6k
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
Rewrite DOMPropertyOperations-test in terms of public API #10281
Conversation
expect(stubNode.hasAttribute('data-foo')).toBe(false); | ||
}); | ||
|
||
it('should use mutation method where applicable', () => { |
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 removed this test. It seems like the only mutation method we use is value
, and AFAIK there are plenty tests about it specifically. Internally at FB we don’t inject custom properties with mutation methods either.
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.
👍. Long term, I believe we can remove mutation method all together if we circle back to #10150
expect(stubNode.hasAttribute('hidden')).toBe(false); | ||
}); | ||
|
||
it('should remove property properly even with different name', () => { |
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 removed this test. I couldn’t find any cases where property name would differ in practice—neither in master nor internally at FB. If we add such case we should add an integration test for that specific property instead.
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, this doesn't happen for any prop that must be assigned as a DOM property. The only thing we alias are a few HTML attribute names and a bunch of SVG attribute names.
expect(stubNode.value).toBe(''); | ||
}); | ||
|
||
it('should not leave all options selected when deleting multiple', () => { |
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 couldn’t figure out how to write this test in terms of public API. I tried rendering <select multiple>
with both value
and defaultValue
, and then rendering <select>
without multiple
but couldn’t get this test to pass. If I need to bring it back, can somebody please explain how? Or maybe this is better suited for DOM fixtures.
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.
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.
Not quite sure what you're asking. The key thing is that multiple individual options will still have selected set to true
in the DOM when removing multiple
as an attribute in some browsers. So you need to render with multiple
and multiple options selected, then remove multiple
and verify that only one option is set in the DOM. The logical way would probably be using an uncontrolled select (because that also avoids controlled behavior potentially fixing it).
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.
Hmm. I tried rendering uncontrolled select and then rendering it without multiple
, and these assertions failed. I'll try again.
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 Doesn't that mean the current implementation is broken then? (EDIT: if we care about fixing it)
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.
Unfortunately I don't know 🤷♀️ We have the old unit test but it's not clear to me whether it is testing the right thing.
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.
The key thing is that multiple individual options will still have selected set to true in the DOM when removing multiple as an attribute in some browsers.
If this behavior is inconsistent, and JSDOM does not exhibit it, do we need to make this a DOM Test Fixture and explore the surface area of browsers that demonstrate it?
DOMPropertyOperations.deleteValueForProperty(stubNode, 'value'); | ||
// JSDOM does not behave correctly for attributes/properties | ||
//expect(stubNode.getAttribute('value')).toBe('foo'); | ||
expect(stubNode.value).toBe(''); |
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.
The diff got a bit messed up. The new test is:
var container = document.createElement('div');
spyOn(console, 'error');
ReactDOM.render(
<input type="text" value="foo" onChange={function() {}} />,
container,
);
ReactDOM.render(
<input type="text" onChange={function() {}} />,
container,
);
expect(container.firstChild.getAttribute('value')).toBe('foo');
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'A component is changing a controlled input of type text to be uncontrolled',
);
But I’m not sure this is right. As you can see, the last line in the old test:
// JSDOM does not behave correctly for attributes/properties
//expect(stubNode.getAttribute('value')).toBe('foo');
expect(stubNode.value).toBe('');
was actually not true anymore. However uncommented line works:
expect(container.firstChild.getAttribute('value')).toBe('foo');
I’m not sure what’s going on here, and whether my test reproduces this case, or not.
What is this really testing?
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.
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.
The new test seems wrong to me, the second render should be <input ... value="" />
to be truthful to the original test-case (it's about preserving the initial attribute value). But AFAIK this changed way back when you made the attribute always reflect the property for input value? So this should fail.
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.
Why did the test pass then, if it’s wrong? Is it because we were testing internals rather than public API?
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.
In other words, I think my test is not faithful to the original test, but is faithful to the actual behavior in 15.
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 this test might be a victim of changes in how value is assigned over the churn trying to fix bugs in controlled inputs. Your change is a good one, but I wonder it is covered by the round of tests starting at:
@syranide I'd appreciate if you could help with the two cases above. I’m trying to figure out how to write correct tests for them that only use |
Sorry. As far as I understand and remember it all; in some browsers As for the input-test I'm not sure what to do, I know what it wants to test. But AFAIK it should not hold anymore (because you intentionally changed that behavior), BUT IIRC you guys decided to backtrack on the logic for syncing properties and attributes in some way. So yeah, I'm not 100% what to do about it. |
The thing I'm struggling with about the If it works just due to the browser (as opposed to React being smart) then I don’t understand what is the value in keeping this test (since it effectively tests jsdom). |
Note I didn’t see a difference—but I probably was testing the wrong thing. All I’m trying to figure out in this thread is whether the test was worth keeping. For now I settled on removing it because I don’t understand how exactly to replicate it with public API. And I’d rather have no test than test that codifies wrong or useless behavior since it could mislead people in the future. |
I don't understand what the jsdom comments are about since they pass.
@syranide @aweary @nhunzaker @jquense Can you please help me out with this? I’d really like to get this test rewrite in. We need to start getting rid of those internal tests that only verify private APIs and don’t test the whole picture. I tried to faithfully reproduce the current behavior. I had to change the thing Can we get it in like this? Or please help me understand how to verify current behavior for these tests. |
expect(stubNode.title).toBe('Tip!'); | ||
var container = document.createElement('div'); | ||
ReactDOM.render(<div title="Tip!" />, container); | ||
expect(container.firstChild.title).toBe('Tip!'); | ||
}); |
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.
What is the advantage to using ReactDOM.render
over TestUtils.renderIntoDocument
?
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.
None. Just seems a bit more flexible.
var container = document.createElement('div'); | ||
ReactDOM.render(<div role="#" />, container); | ||
expect(container.firstChild.getAttribute('role')).toBe('#'); | ||
expect(container.firstChild.role).toBeUndefined(); | ||
}); |
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.
Just thinking: I didn't see a test that covers attributes that require property assignment. I think it would be covered by a test that uses multiple
with a select, but the behavior is probably covered by ReactDOMSelect-test.
To be clear my intention is not to find best solutions for these cases. It is to ensure the existing behavior is still codified in tests when rewritten via public API. So that we don't accidentally regress. |
ReactDOM.render(<div disabled={true} />, container); | ||
expect(container.firstChild.getAttribute('disabled')).toBe(''); | ||
ReactDOM.render(<div disabled={false} />, container); | ||
expect(container.firstChild.getAttribute('disabled')).toBe(null); |
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.
Should this test cover null
too? Is that overkill?
var container = document.createElement('div'); | ||
ReactDOM.render(<div title="foo" />, container); | ||
expect(container.firstChild.getAttribute('title')).toBe('foo'); | ||
expect(container.firstChild.title).toBe('foo'); |
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 is existing test behavior, so take it or leave it, but I think line 143 is essentially testing that JSDOM syncs up the title
property with the title
attribute. Could line 143 go away?
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.
Same deal with line 146.
expect(container.firstChild.value).toBe('foo'); | ||
expect(console.error.calls.count()).toBe(1); | ||
expect(console.error.calls.argsFor(0)[0]).toContain( | ||
'A component is changing a controlled input of type text to be uncontrolled', | ||
); | ||
}); |
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.
If this test is giving us trouble via unit testing, I think we should drop it and consider writing a DOM fixture.
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.
Ah, please lump this into the chat above about selects.
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 this is accurate. I'm curious if the input value
is covered by ReactDOMInput-test, and if the select test should be a fixture, but otherwise 👍
expect(container.firstChild.value).toBe('foo'); | ||
expect(console.error.calls.count()).toBe(1); | ||
expect(console.error.calls.argsFor(0)[0]).toContain( | ||
'A component is changing a controlled input of type text to be uncontrolled', | ||
); |
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.
is this providing any additional coverage over the uncontrolled/controlled switch tests in `ReactDOMInput?
To be clear I deleted the select test. Do you think I should bring it back in some form? |
No. I think deleting it is the right call. |
It's not a React-bug, it's a browser bug. IIRC only older IE, possibly IE9-IE10. |
EDIT: But I think testing that the |
But is it possible to write a test using jsdom and public API to verify React is able to work around this browser bug, even though jsdom does not exhibit it? For example maybe we can set some mocks on some DOM properties to verify React somehow forces them to be right? |
Thanks. Can you elaborate on what it should be set to? |
If we can decide on "toggling
As above, as the component should be recreated when toggling I'm not sure if ReactDOM currently has this behavior, but IMHO it makes sense and how it should be. But I think it does... Makes sense? |
I’m going to land this as is for now. I’ll file a task to follow up with the |
This removes a dependency on Stack-only
ReactDOMComponentTree.precacheNode
so that we can remove it when Stack is gone. For the most part the tests were easy to convert but I’ll point out a few things I wasn’t sure about.