mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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:
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user