Merge branch 'SanderSpies-sspi-dom-attribute-process'

This commit is contained in:
Paul O’Shannessy
2013-10-15 11:56:58 -07:00
7 changed files with 231 additions and 62 deletions
+5 -11
View File
@@ -55,7 +55,7 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
var defaultValue = this.props.defaultValue;
return {
checked: this.props.defaultChecked || false,
value: defaultValue != null && defaultValue !== false ? defaultValue : ''
value: defaultValue != null ? defaultValue : ''
};
},
@@ -74,9 +74,7 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
this.props.checked != null ? this.props.checked : this.state.checked;
var value = this.getValue();
props.value = value != null && value !== false ?
'' + value :
this.state.value;
props.value = value != null ? value : this.state.value;
props.onChange = this._handleChange;
@@ -105,13 +103,9 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
var value = this.getValue();
if (value != null) {
// Cast `this.props.value` to a string so falsey values that cast to
// truthy strings are not ignored.
DOMPropertyOperations.setValueForProperty(
rootNode,
'value',
value !== false ? '' + value : ''
);
// Cast `value` to a string to ensure the value is set correctly. While
// browsers typically do this as necessary, jsdom doesn't.
DOMPropertyOperations.setValueForProperty(rootNode, 'value', '' + value);
}
},
+7 -14
View File
@@ -29,9 +29,6 @@ var merge = require('merge');
// Store a reference to the <textarea> `ReactDOMComponent`.
var textarea = ReactDOM.textarea;
// For quickly matching children type, to test if can be treated as content.
var CONTENT_TYPES = {'string': true, 'number': true};
/**
* Implements a <textarea> native component that allows setting `value`, and
* `defaultValue`. This differs from the traditional DOM API because value is
@@ -72,11 +69,7 @@ var ReactDOMTextarea = ReactCompositeComponent.createClass({
);
children = children[0];
}
invariant(
CONTENT_TYPES[typeof children],
'If you specify children to <textarea>, it must be a single string ' +
'or number., not an array or object.'
);
defaultValue = '' + children;
}
if (defaultValue == null) {
@@ -86,7 +79,9 @@ var ReactDOMTextarea = ReactCompositeComponent.createClass({
return {
// We save the initial value so that `ReactDOMComponent` doesn't update
// `textContent` (unnecessary since we update value).
initialValue: value != null ? value : defaultValue,
// The initial value can be a boolean or object so that's why it's
// forced to be a string.
initialValue: '' + (value != null ? value : defaultValue),
value: defaultValue
};
},
@@ -118,11 +113,9 @@ var ReactDOMTextarea = ReactCompositeComponent.createClass({
componentDidUpdate: function(prevProps, prevState, rootNode) {
var value = this.getValue();
if (value != null) {
DOMPropertyOperations.setValueForProperty(
rootNode,
'value',
value !== false ? '' + value : ''
);
// Cast `value` to a string to ensure the value is set correctly. While
// browsers typically do this as necessary, jsdom doesn't.
DOMPropertyOperations.setValueForProperty(rootNode, 'value', '' + value);
}
},
@@ -50,11 +50,31 @@ describe('ReactDOMInput', function() {
expect(node.value).toBe('0');
});
it('should display "" for `defaultValue` of `false`', function() {
it('should display "true" for `defaultValue` of `true`', function() {
var stub = <input type="text" defaultValue={true} />;
var node = renderTextInput(stub);
expect(node.value).toBe('true');
});
it('should display "false" for `defaultValue` of `false`', function() {
var stub = <input type="text" defaultValue={false} />;
var node = renderTextInput(stub);
expect(node.value).toBe('');
expect(node.value).toBe('false');
});
it('should display "foobar" for `defaultValue` of `objToString`', function() {
var objToString = {
toString: function() {
return "foobar";
}
};
var stub = <input type="text" defaultValue={objToString} />;
var node = renderTextInput(stub);
expect(node.value).toBe('foobar');
});
it('should display `value` of number 0', function() {
@@ -64,11 +84,40 @@ describe('ReactDOMInput', function() {
expect(node.value).toBe('0');
});
it('should display "" for `value` of `false`', function() {
var stub = <input type="text" value={false} />;
it('should allow setting `value` to `true`', function() {
var stub = <input type="text" value="yolo" />;
var node = renderTextInput(stub);
expect(node.value).toBe('');
expect(node.value).toBe('yolo');
stub.replaceProps({value: true});
expect(node.value).toEqual('true');
});
it("should allow setting `value` to `false`", function() {
var stub = <input type="text" value="yolo" />;
var node = renderTextInput(stub);
expect(node.value).toBe('yolo');
stub.replaceProps({value: false});
expect(node.value).toEqual('false');
});
it('should allow setting `value` to `objToString`', function() {
var stub = <input type="text" value="foo" />;
var node = renderTextInput(stub);
expect(node.value).toBe('foo');
var objToString = {
toString: function() {
return "foobar";
}
};
stub.replaceProps({value: objToString});
expect(node.value).toEqual('foobar');
});
it('should properly control a value of number `0`', function() {
@@ -116,6 +116,35 @@ describe('ReactDOMSelect', function() {
expect(node.options[2].selected).toBe(false); // gorilla
});
it('should allow setting `value` with `objectToString`', function() {
var objectToString = {
animal: "giraffe",
toString: function() {
return this.animal;
}
};
var stub =
<select multiple={true} value={[objectToString]}>
<option value="monkey">A monkey!</option>
<option value="giraffe">A giraffe!</option>
<option value="gorilla">A gorilla!</option>
</select>;
var node = renderSelect(stub);
expect(node.options[0].selected).toBe(false); // monkey
expect(node.options[1].selected).toBe(true); // giraffe
expect(node.options[2].selected).toBe(false); // gorilla
// Changing the `value` prop should change the selected options.
objectToString.animal = "monkey";
stub.forceUpdate();
expect(node.options[0].selected).toBe(true); // monkey
expect(node.options[1].selected).toBe(false); // giraffe
expect(node.options[2].selected).toBe(false); // gorilla
});
it('should allow switching to multiple', function() {
var stub =
<select defaultValue="giraffe">
@@ -62,21 +62,24 @@ describe('ReactDOMTextarea', function() {
expect(node.value).toBe('0');
});
it('should display "" for `defaultValue` of `false`', function() {
it('should display "false" for `defaultValue` of `false`', function() {
var stub = <textarea type="text" defaultValue={false} />;
var node = renderTextarea(stub);
expect(node.value).toBe('');
expect(node.value).toBe('false');
});
it('should allow setting `value`', function() {
var stub = <textarea value="giraffe" />;
it('should display "foobar" for `defaultValue` of `objToString`', function() {
var objToString = {
toString: function() {
return "foobar";
}
};
var stub = <textarea type="text" defaultValue={objToString} />;
var node = renderTextarea(stub);
expect(node.value).toBe('giraffe');
stub.replaceProps({value: 'gorilla'});
expect(node.value).toEqual('gorilla');
expect(node.value).toBe('foobar');
});
it('should display `value` of number 0', function() {
@@ -86,11 +89,49 @@ describe('ReactDOMTextarea', function() {
expect(node.value).toBe('0');
});
it('should display "" for `value` of `false`', function() {
var stub = <textarea type="text" value={false} />;
it('should allow setting `value` to `giraffe`', function() {
var stub = <textarea value="giraffe" />;
var node = renderTextarea(stub);
expect(node.value).toBe('');
expect(node.value).toBe('giraffe');
stub.replaceProps({value: 'gorilla'});
expect(node.value).toEqual('gorilla');
});
it('should allow setting `value` to `true`', function() {
var stub = <textarea value="giraffe" />;
var node = renderTextarea(stub);
expect(node.value).toBe('giraffe');
stub.replaceProps({value: true});
expect(node.value).toEqual('true');
});
it('should allow setting `value` to `false`', function() {
var stub = <textarea value="giraffe" />;
var node = renderTextarea(stub);
expect(node.value).toBe('giraffe');
stub.replaceProps({value: false});
expect(node.value).toEqual('false');
});
it('should allow setting `value` to `objToString`', function() {
var stub = <textarea value="giraffe" />;
var node = renderTextarea(stub);
expect(node.value).toBe('giraffe');
var objToString = {
toString: function() {
return "foo";
}
};
stub.replaceProps({value: objToString});
expect(node.value).toEqual('foo');
});
it('should properly control a value of number `0`', function() {
@@ -123,6 +164,25 @@ describe('ReactDOMTextarea', function() {
expect(node.value).toBe('17');
});
it('should allow booleans as children', function() {
spyOn(console, 'warn');
var node = renderTextarea(<textarea>{false}</textarea>);
expect(console.warn.argsForCall.length).toBe(1);
expect(node.value).toBe('false');
});
it('should allow objects as children', function() {
spyOn(console, 'warn');
var obj = {
toString: function() {
return "sharkswithlasers";
}
};
var node = renderTextarea(<textarea>{obj}</textarea>);
expect(console.warn.argsForCall.length).toBe(1);
expect(node.value).toBe('sharkswithlasers');
});
it('should throw with multiple or invalid children', function() {
spyOn(console, 'warn');
@@ -134,11 +194,12 @@ describe('ReactDOMTextarea', function() {
expect(console.warn.argsForCall.length).toBe(1);
var stub;
expect(function() {
ReactTestUtils.renderIntoDocument(
<textarea><strong /></textarea>
);
}).toThrow();
stub = renderTextarea(<textarea><strong /></textarea>);
}).not.toThrow();
expect(stub.value).toBe('[object Object]');
expect(console.warn.argsForCall.length).toBe(2);
});
@@ -0,0 +1,56 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @emails react-core
*/
"use strict";
describe('escapeTextForBrowser', function() {
var escapeTextForBrowser = require('escapeTextForBrowser');
it('should escape boolean to string', function() {
expect(escapeTextForBrowser(true)).toBe('true');
expect(escapeTextForBrowser(false)).toBe('false');
});
it('should escape object to string', function() {
var escaped = escapeTextForBrowser({
toString: function() {
return 'ponys';
}
});
expect(escaped).toBe('ponys');
});
it('should escape number to string', function() {
expect(escapeTextForBrowser(42)).toBe('42');
});
it('should escape string', function() {
var escaped = escapeTextForBrowser('<script type=\'\' src=""></script>');
expect(escaped).not.toContain('<');
expect(escaped).not.toContain('>');
expect(escaped).not.toContain('\'');
expect(escaped).not.toContain('/');
expect(escaped).not.toContain('\"');
escaped = escapeTextForBrowser('&');
expect(escaped).toBe('&amp;');
});
});
+4 -17
View File
@@ -19,8 +19,6 @@
"use strict";
var invariant = require('invariant');
var ESCAPE_LOOKUP = {
"&": "&amp;",
">": "&gt;",
@@ -30,6 +28,8 @@ var ESCAPE_LOOKUP = {
"/": "&#x2f;"
};
var ESCAPE_REGEX = /[&><"'\/]/g;
function escaper(match) {
return ESCAPE_LOOKUP[match];
}
@@ -37,24 +37,11 @@ function escaper(match) {
/**
* Escapes text to prevent scripting attacks.
*
* @param {number|string} text Text value to escape.
* @param {*} text Text value to escape.
* @return {string} An escaped string.
*/
function escapeTextForBrowser(text) {
var type = typeof text;
invariant(
type !== 'object',
'escapeTextForBrowser(...): Attempted to escape an object.'
);
if (text === '') {
return '';
} else {
if (type === 'string') {
return text.replace(/[&><"'\/]/g, escaper);
} else {
return (''+text).replace(/[&><"'\/]/g, escaper);
}
}
return ('' + text).replace(ESCAPE_REGEX, escaper);
}
module.exports = escapeTextForBrowser;