Rewrite DOMPropertyOperations-test in terms of public API (#10281)

* Rewrite DOMPropertyOperations-test in terms of public API

* Add more assertions

I don't understand what the jsdom comments are about since they pass.

* Address review feedback
This commit is contained in:
Dan Abramov
2017-08-07 20:12:13 +01:00
committed by GitHub
parent 7aec5f8f49
commit 4e4653da9b
@@ -12,67 +12,54 @@
'use strict';
describe('DOMPropertyOperations', () => {
var DOMPropertyOperations;
var DOMProperty;
var ReactDOMComponentTree;
var React;
var ReactDOM;
beforeEach(() => {
jest.resetModules();
require('ReactDOMInjection');
// TODO: can we express this test with only public API?
DOMPropertyOperations = require('DOMPropertyOperations');
DOMProperty = require('DOMProperty');
ReactDOMComponentTree = require('ReactDOMComponentTree');
React = require('react');
ReactDOM = require('react-dom');
});
describe('setValueForProperty', () => {
var stubNode;
var stubInstance;
beforeEach(() => {
stubNode = document.createElement('div');
stubInstance = {_debugID: 1};
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
});
it('should set values as properties by default', () => {
DOMPropertyOperations.setValueForProperty(stubNode, 'title', 'Tip!');
expect(stubNode.title).toBe('Tip!');
var container = document.createElement('div');
ReactDOM.render(<div title="Tip!" />, container);
expect(container.firstChild.title).toBe('Tip!');
});
it('should set values as attributes if necessary', () => {
DOMPropertyOperations.setValueForProperty(stubNode, 'role', '#');
expect(stubNode.getAttribute('role')).toBe('#');
expect(stubNode.role).toBeUndefined();
var container = document.createElement('div');
ReactDOM.render(<div role="#" />, container);
expect(container.firstChild.getAttribute('role')).toBe('#');
expect(container.firstChild.role).toBeUndefined();
});
it('should set values as namespace attributes if necessary', () => {
spyOn(stubNode, 'setAttributeNS');
DOMPropertyOperations.setValueForProperty(
stubNode,
'xlinkHref',
'about:blank',
);
expect(stubNode.setAttributeNS.calls.count()).toBe(1);
expect(stubNode.setAttributeNS.calls.argsFor(0)).toEqual([
'http://www.w3.org/1999/xlink',
'xlink:href',
'about:blank',
]);
var container = document.createElement('svg');
ReactDOM.render(<image xlinkHref="about:blank" />, container);
expect(
container.firstChild.getAttributeNS(
'http://www.w3.org/1999/xlink',
'href',
),
).toBe('about:blank');
});
it('should set values as boolean properties', () => {
DOMPropertyOperations.setValueForProperty(
stubNode,
'disabled',
'disabled',
);
expect(stubNode.getAttribute('disabled')).toBe('');
DOMPropertyOperations.setValueForProperty(stubNode, 'disabled', true);
expect(stubNode.getAttribute('disabled')).toBe('');
DOMPropertyOperations.setValueForProperty(stubNode, 'disabled', false);
expect(stubNode.getAttribute('disabled')).toBe(null);
var container = document.createElement('div');
ReactDOM.render(<div disabled="disabled" />, container);
expect(container.firstChild.getAttribute('disabled')).toBe('');
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);
ReactDOM.render(<div disabled={true} />, container);
ReactDOM.render(<div disabled={null} />, container);
expect(container.firstChild.getAttribute('disabled')).toBe(null);
ReactDOM.render(<div disabled={true} />, container);
ReactDOM.render(<div disabled={undefined} />, container);
expect(container.firstChild.getAttribute('disabled')).toBe(null);
});
it('should convert attribute values to string first', () => {
@@ -83,181 +70,104 @@ describe('DOMPropertyOperations', () => {
return '<html>';
},
};
DOMPropertyOperations.setValueForProperty(stubNode, 'role', obj);
expect(stubNode.getAttribute('role')).toBe('<html>');
var container = document.createElement('div');
ReactDOM.render(<div role={obj} />, container);
expect(container.firstChild.getAttribute('role')).toBe('<html>');
});
it('should not remove empty attributes for special properties', () => {
stubNode = document.createElement('input');
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
DOMPropertyOperations.setValueForProperty(stubNode, 'value', '');
// JSDOM does not behave correctly for attributes/properties
//expect(stubNode.getAttribute('value')).toBe('');
expect(stubNode.value).toBe('');
var container = document.createElement('div');
ReactDOM.render(<input value="" />, container);
expect(container.firstChild.getAttribute('value')).toBe('');
expect(container.firstChild.value).toBe('');
});
it('should remove for falsey boolean properties', () => {
DOMPropertyOperations.setValueForProperty(
stubNode,
'allowFullScreen',
false,
);
expect(stubNode.hasAttribute('allowFullScreen')).toBe(false);
var container = document.createElement('div');
ReactDOM.render(<div allowFullScreen={false} />, container);
expect(container.firstChild.hasAttribute('allowFullScreen')).toBe(false);
});
it('should remove when setting custom attr to null', () => {
DOMPropertyOperations.setValueForProperty(stubNode, 'data-foo', 'bar');
expect(stubNode.hasAttribute('data-foo')).toBe(true);
DOMPropertyOperations.setValueForProperty(stubNode, 'data-foo', null);
expect(stubNode.hasAttribute('data-foo')).toBe(false);
});
it('should use mutation method where applicable', () => {
var foobarSetter = jest.fn();
// inject foobar DOM property
DOMProperty.injection.injectDOMPropertyConfig({
Properties: {foobar: null},
DOMMutationMethods: {
foobar: foobarSetter,
},
});
DOMPropertyOperations.setValueForProperty(
stubNode,
'foobar',
'cows say moo',
);
expect(foobarSetter.mock.calls.length).toBe(1);
expect(foobarSetter.mock.calls[0][0]).toBe(stubNode);
expect(foobarSetter.mock.calls[0][1]).toBe('cows say moo');
var container = document.createElement('div');
ReactDOM.render(<div data-foo="bar" />, container);
expect(container.firstChild.hasAttribute('data-foo')).toBe(true);
ReactDOM.render(<div data-foo={null} />, container);
expect(container.firstChild.hasAttribute('data-foo')).toBe(false);
});
it('should set className to empty string instead of null', () => {
DOMPropertyOperations.setValueForProperty(
stubNode,
'className',
'selected',
);
expect(stubNode.className).toBe('selected');
DOMPropertyOperations.setValueForProperty(stubNode, 'className', null);
var container = document.createElement('div');
ReactDOM.render(<div className="selected" />, container);
expect(container.firstChild.className).toBe('selected');
ReactDOM.render(<div className={null} />, container);
// className should be '', not 'null' or null (which becomes 'null' in
// some browsers)
expect(stubNode.className).toBe('');
expect(stubNode.getAttribute('class')).toBe(null);
expect(container.firstChild.className).toBe('');
expect(container.firstChild.getAttribute('class')).toBe(null);
});
it('should remove property properly for boolean properties', () => {
DOMPropertyOperations.setValueForProperty(stubNode, 'hidden', true);
expect(stubNode.hasAttribute('hidden')).toBe(true);
DOMPropertyOperations.setValueForProperty(stubNode, 'hidden', false);
expect(stubNode.hasAttribute('hidden')).toBe(false);
});
it('should remove property properly even with different name', () => {
// Suppose 'foobar' is a property that corresponds to the underlying
// 'className' property:
DOMProperty.injection.injectDOMPropertyConfig({
Properties: {foobar: DOMProperty.injection.MUST_USE_PROPERTY},
DOMPropertyNames: {
foobar: 'className',
},
DOMAttributeNames: {
foobar: 'class',
},
});
DOMPropertyOperations.setValueForProperty(stubNode, 'foobar', 'selected');
expect(stubNode.className).toBe('selected');
DOMPropertyOperations.setValueForProperty(stubNode, 'foobar', null);
// className should be '', not 'null' or null (which becomes 'null' in
// some browsers)
expect(stubNode.className).toBe('');
var container = document.createElement('div');
ReactDOM.render(<div hidden={true} />, container);
expect(container.firstChild.hasAttribute('hidden')).toBe(true);
ReactDOM.render(<div hidden={false} />, container);
expect(container.firstChild.hasAttribute('hidden')).toBe(false);
});
});
describe('value mutation method', function() {
it('should update an empty attribute to zero', function() {
var stubNode = document.createElement('input');
var stubInstance = {_debugID: 1};
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
stubNode.setAttribute('type', 'radio');
DOMPropertyOperations.setValueForProperty(stubNode, 'value', '');
spyOn(stubNode, 'setAttribute');
DOMPropertyOperations.setValueForProperty(stubNode, 'value', 0);
expect(stubNode.setAttribute.calls.count()).toBe(1);
var container = document.createElement('div');
ReactDOM.render(
<input type="radio" value="" onChange={function() {}} />,
container,
);
spyOn(container.firstChild, 'setAttribute');
ReactDOM.render(
<input type="radio" value={0} onChange={function() {}} />,
container,
);
expect(container.firstChild.setAttribute.calls.count()).toBe(1);
});
it('should always assign the value attribute for non-inputs', function() {
var stubNode = document.createElement('progress');
var stubInstance = {_debugID: 1};
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
spyOn(stubNode, 'setAttribute');
DOMPropertyOperations.setValueForProperty(stubNode, 'value', 30);
DOMPropertyOperations.setValueForProperty(stubNode, 'value', '30');
expect(stubNode.setAttribute.calls.count()).toBe(2);
var container = document.createElement('div');
ReactDOM.render(<progress />, container);
spyOn(container.firstChild, 'setAttribute');
ReactDOM.render(<progress value={30} />, container);
ReactDOM.render(<progress value="30" />, container);
expect(container.firstChild.setAttribute.calls.count()).toBe(2);
});
});
describe('deleteValueForProperty', () => {
var stubNode;
var stubInstance;
beforeEach(() => {
stubNode = document.createElement('div');
stubInstance = {_debugID: 1};
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
});
it('should remove attributes for normal properties', () => {
DOMPropertyOperations.setValueForProperty(stubNode, 'title', 'foo');
expect(stubNode.getAttribute('title')).toBe('foo');
expect(stubNode.title).toBe('foo');
DOMPropertyOperations.deleteValueForProperty(stubNode, 'title');
expect(stubNode.getAttribute('title')).toBe(null);
// JSDOM does not behave correctly for attributes/properties
//expect(stubNode.title).toBe('');
var container = document.createElement('div');
ReactDOM.render(<div title="foo" />, container);
expect(container.firstChild.getAttribute('title')).toBe('foo');
ReactDOM.render(<div />, container);
expect(container.firstChild.getAttribute('title')).toBe(null);
});
it('should not remove attributes for special properties', () => {
stubNode = document.createElement('input');
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
stubNode.setAttribute('value', 'foo');
DOMPropertyOperations.deleteValueForProperty(stubNode, 'value');
// JSDOM does not behave correctly for attributes/properties
//expect(stubNode.getAttribute('value')).toBe('foo');
expect(stubNode.value).toBe('');
});
it('should not leave all options selected when deleting multiple', () => {
stubNode = document.createElement('select');
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
stubNode.multiple = true;
stubNode.appendChild(document.createElement('option'));
stubNode.appendChild(document.createElement('option'));
stubNode.options[0].selected = true;
stubNode.options[1].selected = true;
DOMPropertyOperations.deleteValueForProperty(stubNode, 'multiple');
expect(stubNode.getAttribute('multiple')).toBe(null);
expect(stubNode.multiple).toBe(false);
expect(stubNode.options[0].selected && stubNode.options[1].selected).toBe(
false,
var container = document.createElement('div');
spyOn(console, 'error');
ReactDOM.render(
<input type="text" value="foo" onChange={function() {}} />,
container,
);
expect(container.firstChild.getAttribute('value')).toBe('foo');
expect(container.firstChild.value).toBe('foo');
ReactDOM.render(
<input type="text" onChange={function() {}} />,
container,
);
expect(container.firstChild.getAttribute('value')).toBe('foo');
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',
);
});
});