diff --git a/.babelrc b/.babelrc index 2ff815b4253..a4b2aee5899 100644 --- a/.babelrc +++ b/.babelrc @@ -22,6 +22,7 @@ "transform-es2015-modules-commonjs", "transform-es3-member-expression-literals", "transform-es3-property-literals", - "./scripts/babel/transform-object-assign-require" + "./scripts/babel/transform-object-assign-require", + "babel-preset-react/node_modules/babel-plugin-transform-react-jsx-source" ] } diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index a9b30e1ca87..21d41414dd4 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -29,6 +29,7 @@ var ReactDOMButton = require('ReactDOMButton'); var ReactDOMComponentFlags = require('ReactDOMComponentFlags'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactDOMInput = require('ReactDOMInput'); +var ReactDOMInstrumentation = require('ReactDOMInstrumentation'); var ReactDOMOption = require('ReactDOMOption'); var ReactDOMSelect = require('ReactDOMSelect'); var ReactDOMTextarea = require('ReactDOMTextarea'); @@ -579,6 +580,10 @@ ReactDOMComponent.Mixin = { validateDOMNesting.updatedAncestorInfo(parentInfo, this._tag, this); } + if (__DEV__) { + ReactDOMInstrumentation.debugTool.onMountDOMComponent(this._debugID, this._currentElement); + } + var mountImage; if (transaction.useCreateElement) { var ownerDocument = nativeContainerInfo._ownerDocument; @@ -854,6 +859,10 @@ ReactDOMComponent.Mixin = { context ); + if (__DEV__) { + ReactDOMInstrumentation.debugTool.onUpdateDOMComponent(this._debugID, this._currentElement); + } + if (this._tag === 'select') { // ); + expect(console.error.argsForCall.length).toBe(2); + expect(console.error.argsForCall[0][0]).toMatch(/.*className.*\(.*:\d+\)/); + expect(console.error.argsForCall[1][0]).toMatch(/.*onClick.*\(.*:\d+\)/); + }); + + it('gives source code refs for unknown prop warning for update render', function() { + spyOn(console, 'error'); + var container = document.createElement('div'); + + ReactDOMServer.renderToString(
, container); + expect(console.error.argsForCall.length).toBe(0); + + ReactDOMServer.renderToString(
, container); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toMatch(/.*className.*\(.*:\d+\)/); + }); + + it('gives source code refs for unknown prop warning for exact elements ', function() { + spyOn(console, 'error'); + + ReactDOMServer.renderToString( +
+
+
+
+
+
+
+ ); + + expect(console.error.argsForCall.length).toBe(2); + + var matches = console.error.argsForCall[0][0].match(/.*className.*\(.*:(\d+)\)/); + var previousLine = matches[1]; + + matches = console.error.argsForCall[1][0].match(/.*onClick.*\(.*:(\d+)\)/); + var currentLine = matches[1]; + + //verify line number has a proper relative difference, + //since hard coding the line number would make test too brittle + expect(parseInt(previousLine, 10) + 2).toBe(parseInt(currentLine, 10)); + }); + + it('gives source code refs for unknown prop warning for exact elements in composition ', function() { + spyOn(console, 'error'); + var container = document.createElement('div'); + + var Parent = React.createClass({ + render: function() { + return
; + }, + }); + + var Child1 = React.createClass({ + render: function() { + return
Child1
; + }, + }); + + var Child2 = React.createClass({ + render: function() { + return
Child2
; + }, + }); + + var Child3 = React.createClass({ + render: function() { + return
Child3
; + }, + }); + + var Child4 = React.createClass({ + render: function() { + return
Child4
; + }, + }); + + ReactDOMServer.renderToString(, container); + + expect(console.error.argsForCall.length).toBe(2); + + var matches = console.error.argsForCall[0][0].match(/.*className.*\(.*:(\d+)\)/); + var previousLine = matches[1]; + + matches = console.error.argsForCall[1][0].match(/.*onClick.*\(.*:(\d+)\)/); + var currentLine = matches[1]; + + //verify line number has a proper relative difference, + //since hard coding the line number would make test too brittle + expect(parseInt(previousLine, 10) + 12).toBe(parseInt(currentLine, 10)); + + }); }); }); diff --git a/src/renderers/dom/shared/devtools/ReactDOMUnknownPropertyDevtool.js b/src/renderers/dom/shared/devtools/ReactDOMUnknownPropertyDevtool.js index 5adc359063e..5518b3b1ecf 100644 --- a/src/renderers/dom/shared/devtools/ReactDOMUnknownPropertyDevtool.js +++ b/src/renderers/dom/shared/devtools/ReactDOMUnknownPropertyDevtool.js @@ -17,6 +17,7 @@ var EventPluginRegistry = require('EventPluginRegistry'); var warning = require('warning'); if (__DEV__) { + var cachedSource; var reactProps = { children: true, dangerouslySetInnerHTML: true, @@ -50,9 +51,10 @@ if (__DEV__) { // logging too much when using transferPropsTo. warning( standardName == null, - 'Unknown DOM property %s. Did you mean %s?', + 'Unknown DOM property %s. Did you mean %s? %s', name, - standardName + standardName, + formatSource(cachedSource) ); var registrationName = ( @@ -65,11 +67,17 @@ if (__DEV__) { warning( registrationName == null, - 'Unknown event handler property %s. Did you mean `%s`?', + 'Unknown event handler property %s. Did you mean `%s`? %s', name, - registrationName + registrationName, + formatSource(cachedSource) ); }; + + var formatSource = function(source) { + return source ? `(${source.fileName.replace(/^.*[\\\/]/, '')}:${source.lineNumber})` : ''; + }; + } var ReactDOMUnknownPropertyDevtool = { @@ -82,6 +90,12 @@ var ReactDOMUnknownPropertyDevtool = { onDeleteValueForProperty(node, name) { warnUnknownProperty(name); }, + onMountDOMComponent(debugID, element) { + cachedSource = element ? element._source : null; + }, + onUpdateDOMComponent(debugID, element) { + cachedSource = element ? element._source : null; + }, }; module.exports = ReactDOMUnknownPropertyDevtool;