/** * Copyright 2013-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @emails react-core */ /*jshint evil:true, unused:false*/ 'use strict'; require('mock-modules').autoMockOff(); var transformFn = require('jstransform').transform; var visitors = require('../react-jsx-visitors').visitorList; function transform(code) { return transformFn(visitors, code); } describe('react jsx', function() { // These are placeholder variables in scope that we can use to assert that a // specific variable reference was passed, rather than an object clone of it. var x = 123456; var y = 789012; var z = 345678; var expectObjectAssign = function(code) { /*eslint-disable no-unused-vars, no-eval*/ var Component = jest.genMockFunction(); var Child = jest.genMockFunction(); var objectAssignMock = jest.genMockFunction(); React.__spread = objectAssignMock; eval(transform(code).code); return expect(objectAssignMock); /*eslint-enable*/ }; var React = { createElement: jest.genMockFunction() }; it('should convert simple tags', function() { var code = 'var x =
;'; var result = 'var x = React.createElement("div", null);'; expect(transform(code).code).toEqual(result); }); it('should convert simple text', function() { var code = 'var x =
text
;'; var result = 'var x = React.createElement("div", null, "text");'; expect(transform(code).code).toEqual(result); }); it('should have correct comma in nested children', function() { var code = [ 'var x =
', '

', ' {foo}
{bar}
', '
', '
;' ].join('\n'); var result = [ 'var x = React.createElement("div", null, ', ' React.createElement("div", null, ' + 'React.createElement("br", null)), ', ' React.createElement(Component, null, foo, ' + 'React.createElement("br", null), bar), ', ' React.createElement("br", null)', ');' ].join('\n'); expect(transform(code).code).toEqual(result); }); it('should avoid wrapping in extra parens if not needed', function() { // Try with a single composite child, wrapped in a div. var code = [ 'var x =
', ' ', '
;' ].join('\n'); var result = [ 'var x = React.createElement("div", null, ', ' React.createElement(Component, null)', ');' ].join('\n'); expect(transform(code).code).toEqual(result); // Try with a single interpolated child, wrapped in a div. code = [ 'var x =
', ' {this.props.children}', '
;' ].join('\n'); result = [ 'var x = React.createElement("div", null, ', ' this.props.children', ');' ].join('\n'); expect(transform(code).code).toEqual(result); // Try with a single interpolated child, wrapped in a composite. code = [ 'var x = ', ' {this.props.children}', ';' ].join('\n'); result = [ 'var x = React.createElement(Composite, null, ', ' this.props.children', ');' ].join('\n'); expect(transform(code).code).toEqual(result); // Try with a single composite child, wrapped in a composite. code = [ 'var x = ', ' ', ';' ].join('\n'); result = [ 'var x = React.createElement(Composite, null, ', ' React.createElement(Composite2, null)', ');' ].join('\n'); expect(transform(code).code).toEqual(result); }); it('should insert commas after expressions before whitespace', function() { var code = [ 'var x =', ' ', ' ;' ].join('\n'); var result = [ 'var x =', ' React.createElement("div", {', ' attr1: ', ' "foo" + "bar", ', ' ', ' attr2: ', ' "foo" + "bar" +', ' ', ' "baz" + "bug", ', ' ', ' attr3: ', ' "foo" + "bar" +', ' "baz" + "bug", ', ' // Extra line here.', ' ', ' attr4: "baz"}', ' );' ].join('\n'); expect(transform(code).code).toEqual(result); }); it('should properly handle comments adjacent to children', function() { var code = [ 'var x = (', '
', ' {/* A comment at the beginning */}', ' {/* A second comment at the beginning */}', ' ', ' {/* A nested comment */}', ' ', ' {/* A sandwiched comment */}', '
', ' {/* A comment at the end */}', ' {/* A second comment at the end */}', '
', ');' ].join('\n'); var result = [ 'var x = (', ' React.createElement("div", null, ', ' /* A comment at the beginning */', ' /* A second comment at the beginning */', ' React.createElement("span", null', ' /* A nested comment */', ' ), ', ' /* A sandwiched comment */', ' React.createElement("br", null)', ' /* A comment at the end */', ' /* A second comment at the end */', ' )', ');' ].join('\n'); expect(transform(code).code).toBe(result); }); it('should properly handle comments between props', function() { var code = [ 'var x = (', ' ', ' ', ' ', ');' ].join('\n'); var result = [ 'var x = (', ' React.createElement("div", {', ' /* a multi-line', ' comment */', ' attr1: "foo"}, ', ' React.createElement("span", {// a double-slash comment', ' attr2: "bar"}', ' )', ' )', ');' ].join('\n'); expect(transform(code).code).toBe(result); }); it('should not strip tags with a single child of  ', function() { var code = [ '
 
;' ].join('\n'); var result = [ 'React.createElement("div", null, "\u00A0");' ].join('\n'); expect(transform(code).code).toBe(result); }); it('should not strip   even coupled with other whitespace', function() { var code = [ '
 
;' ].join('\n'); var result = [ 'React.createElement("div", null, "\u00A0 ");' ].join('\n'); expect(transform(code).code).toBe(result); }); it('should handle hasOwnProperty correctly', function() { var code = 'testing;'; var result = 'React.createElement("hasOwnProperty", null, "testing");'; expect(transform(code).code).toBe(result); }); it('should allow constructor as prop', function() { var code = ';'; var result = 'React.createElement(Component, {constructor: "foo"});'; expect(transform(code).code).toBe(result); }); it('should allow JS namespacing', function() { var code = ';'; var result = 'React.createElement(Namespace.Component, null);'; expect(transform(code).code).toBe(result); }); it('should allow deeper JS namespacing', function() { var code = ';'; var result = 'React.createElement(Namespace.DeepNamespace.Component, null);'; expect(transform(code).code).toBe(result); }); it('should disallow XML namespacing', function() { var code = ';'; expect(() => transform(code)).toThrow(); }); it('wraps props in React.__spread for spread attributes', function() { var code = ''; var result = 'React.createElement(Component, React.__spread({}, x , {y: \n' + '2, z: true}))'; expect(transform(code).code).toBe(result); }); it('adds appropriate newlines when using spread attribute', function() { var code = ''; var result = 'React.createElement(Component, React.__spread({}, \n' + ' this.props, \n' + ' {sound: "moo"}))'; expect(transform(code).code).toBe(result); }); it('handles overparenthesized JS', function() { var code = 'Foo {(e+f //A line comment\n' + '/* A multiline comment */)\n' + '} bar\n' + ''; var result = 'React.createElement("foo", {a: (b), c: (d)}, "Foo ", (e+f //A line comment\n' + '/* A multiline comment */), \n' + '" bar"\n' + ')'; expect(transform(code).code).toBe(result); }); it('should transform known hyphenated tags', function() { var code = ';'; var result = 'React.createElement("font-face", null);'; expect(transform(code).code).toBe(result); }); it('does not call React.__spread when there are no spreads', function() { expectObjectAssign( '' ).not.toBeCalled(); }); it('should not throw for unknown hyphenated tags', function() { var code = ';'; expect(function() { transform(code); }).not.toThrow(); }); it('calls assign with a new target object for spreads', function() { expectObjectAssign( '' ).toBeCalledWith({}, x); }); it('calls assign with an empty object when the spread is first', function() { expectObjectAssign( '' ).toBeCalledWith({}, x, {y: 2}); }); it('coalesces consecutive properties into a single object', function() { expectObjectAssign( '' ).toBeCalledWith({}, x, {y: 2, z: true}); }); it('avoids an unnecessary empty object when spread is not first', function() { expectObjectAssign( '' ).toBeCalledWith({x: 1}, y); }); it('passes the same value multiple times to React.__spread', function() { expectObjectAssign( '' ).toBeCalledWith({x: 1, y: '2'}, z, z); }); it('evaluates sequences before passing them to React.__spread', function() { expectObjectAssign( 'Text' ).toBeCalledWith({x: '1'}, {y: 2}, {z: 3}); }); });