diff --git a/src/core/__tests__/jsxns-test.js b/src/core/__tests__/jsxns-test.js new file mode 100644 index 0000000000000..c362ddf33fb03 --- /dev/null +++ b/src/core/__tests__/jsxns-test.js @@ -0,0 +1,89 @@ +/** + * Copyright 2013-2014 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. + * + * @jsx React.DOM + * @jsxns {"MyNamespace":"SampleNamespace"} + * @emails react-core + */ + +"use strict"; + +var React = require('React'); +var ReactTestUtils = require('ReactTestUtils'); + +var reactComponentExpect= require('reactComponentExpect'); + +var SampleNamespace = { + ComponentA: React.createClass({ + render: function() { + return ( + +

Test

+
+ ); + } + }), + + ComponentB: React.createClass({ + render: function() { + return ( +
+ {this.props.children} +
+ ); + } + }) +}; + +var renderJSXNSComponent = function() { + var testJSXNSComponent = + ReactTestUtils.renderIntoDocument(); + + reactComponentExpect(testJSXNSComponent) + .toBeCompositeComponentWithType(SampleNamespace.ComponentA); + + var generalContainer = testJSXNSComponent.refs.childComponent; + + reactComponentExpect(generalContainer) + .toBeCompositeComponentWithType(SampleNamespace.ComponentB); + + return testJSXNSComponent; +}; + +describe('jsxns', function() { + beforeEach(function() { + require('mock-modules').dumpCache(); + }); + + /** + * Ensure that component respects namespace correctly. + */ + it("Should render namespaced components correctly", function() { + var testJSXNSComponent = renderJSXNSComponent(); + + var child = ReactTestUtils.findRenderedDOMComponentWithClass(testJSXNSComponent, 'wrapper'); + + expect(child).toBeDefined(); + }); + + /** + * Fails for undefined namespaces. + */ + it("Should fail for undefined namespace", function() { + expect(function() { + ReactTestUtils.renderIntoDocument(); + }).toThrow(); + }); +}); diff --git a/vendor/fbtransform/transforms/react.js b/vendor/fbtransform/transforms/react.js index 2e50fb9b33108..85850016eac95 100644 --- a/vendor/fbtransform/transforms/react.js +++ b/vendor/fbtransform/transforms/react.js @@ -47,22 +47,54 @@ var JSX_ATTRIBUTE_TRANSFORMS = { } }; -function visitReactTag(traverse, object, path, state) { +function loadJSXNamespaces(state) { + var namespaces = {}; + var jsxns = utils.getDocblock(state).jsxns; + + if (jsxns) { + try { + namespaces = JSON.parse(jsxns); + } catch (e) { + throw new SyntaxError('Error parsing @jsxns docblock: ' + e.message); + } + } + + return namespaces; +} + +function normalizeTag(nameObject, state) { var jsxObjIdent = utils.getDocblock(state).jsx; + var jsxNamespaces = loadJSXNamespaces(state); + var isFallbackTag = FALLBACK_TAGS.hasOwnProperty(nameObject.name); + var namespace = nameObject.namespace; + + if (isFallbackTag) { + namespace = jsxObjIdent; + } else if (jsxNamespaces.hasOwnProperty(namespace)) { + namespace = jsxNamespaces[namespace]; + } else if (namespace) { + throw new Error( + 'Undefined namespace used on tag: ' + namespace + ':' + nameObject.name + + '. Define any custom namespaces with @jsxns.'); + } + + if (namespace) { + return namespace + '.' + nameObject.name; + } else { + return nameObject.name; + } +} + +function visitReactTag(traverse, object, path, state) { var openingElement = object.openingElement; var nameObject = openingElement.name; var attributesObject = openingElement.attributes; + var tagIdentifier = normalizeTag(nameObject, state); utils.catchup(openingElement.range[0], state); - if (nameObject.namespace) { - throw new Error( - 'Namespace tags are not supported. ReactJSX is not XML.'); - } - - var isFallbackTag = FALLBACK_TAGS.hasOwnProperty(nameObject.name); utils.append( - (isFallbackTag ? jsxObjIdent + '.' : '') + (nameObject.name) + '(', + tagIdentifier + '(', state );