Rewrite DOMPropertyOperations-test in terms of public API#10281
Rewrite DOMPropertyOperations-test in terms of public API#10281gaearon merged 3 commits intofacebook:masterfrom
Conversation
| expect(stubNode.hasAttribute('data-foo')).toBe(false); | ||
| }); | ||
|
|
||
| it('should use mutation method where applicable', () => { |
There was a problem hiding this comment.
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.
👍. 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.
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.
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.
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.
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.
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.
@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.
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.
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.
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.
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.
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.
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.
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. |
| var container = document.createElement('div'); | ||
| ReactDOM.render(<div title="Tip!" />, container); | ||
| expect(container.firstChild.title).toBe('Tip!'); | ||
| }); |
There was a problem hiding this comment.
What is the advantage to using ReactDOM.render over TestUtils.renderIntoDocument?
There was a problem hiding this comment.
None. Just seems a bit more flexible.
| ReactDOM.render(<div role="#" />, container); | ||
| expect(container.firstChild.getAttribute('role')).toBe('#'); | ||
| expect(container.firstChild.role).toBeUndefined(); | ||
| }); |
There was a problem hiding this comment.
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.
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.
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?
| 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.
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.
Ah, please lump this into the chat above about selects.
nhunzaker
left a comment
There was a problem hiding this comment.
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(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.
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.precacheNodeso 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.