react-jsx-test.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /**
  2. * Copyright 2013-present, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. *
  9. * @emails react-core
  10. */
  11. /*jshint evil:true, unused:false*/
  12. 'use strict';
  13. require('mock-modules').autoMockOff();
  14. var transformFn = require('jstransform').transform;
  15. var visitors = require('../react-jsx-visitors').visitorList;
  16. function transform(code) {
  17. return transformFn(visitors, code);
  18. }
  19. describe('react jsx', function() {
  20. // These are placeholder variables in scope that we can use to assert that a
  21. // specific variable reference was passed, rather than an object clone of it.
  22. var x = 123456;
  23. var y = 789012;
  24. var z = 345678;
  25. var expectObjectAssign = function(code) {
  26. /*eslint-disable no-unused-vars, no-eval*/
  27. var Component = jest.genMockFunction();
  28. var Child = jest.genMockFunction();
  29. var objectAssignMock = jest.genMockFunction();
  30. React.__spread = objectAssignMock;
  31. eval(transform(code).code);
  32. return expect(objectAssignMock);
  33. /*eslint-enable*/
  34. };
  35. var React = {
  36. createElement: jest.genMockFunction()
  37. };
  38. it('should convert simple tags', function() {
  39. var code = 'var x = <div></div>;';
  40. var result = 'var x = React.createElement("div", null);';
  41. expect(transform(code).code).toEqual(result);
  42. });
  43. it('should convert simple text', function() {
  44. var code = 'var x = <div>text</div>;';
  45. var result = 'var x = React.createElement("div", null, "text");';
  46. expect(transform(code).code).toEqual(result);
  47. });
  48. it('should have correct comma in nested children', function() {
  49. var code = [
  50. 'var x = <div>',
  51. ' <div><br /></div>',
  52. ' <Component>{foo}<br />{bar}</Component>',
  53. ' <br />',
  54. '</div>;'
  55. ].join('\n');
  56. var result = [
  57. 'var x = React.createElement("div", null, ',
  58. ' React.createElement("div", null, ' +
  59. 'React.createElement("br", null)), ',
  60. ' React.createElement(Component, null, foo, ' +
  61. 'React.createElement("br", null), bar), ',
  62. ' React.createElement("br", null)',
  63. ');'
  64. ].join('\n');
  65. expect(transform(code).code).toEqual(result);
  66. });
  67. it('should avoid wrapping in extra parens if not needed', function() {
  68. // Try with a single composite child, wrapped in a div.
  69. var code = [
  70. 'var x = <div>',
  71. ' <Component />',
  72. '</div>;'
  73. ].join('\n');
  74. var result = [
  75. 'var x = React.createElement("div", null, ',
  76. ' React.createElement(Component, null)',
  77. ');'
  78. ].join('\n');
  79. expect(transform(code).code).toEqual(result);
  80. // Try with a single interpolated child, wrapped in a div.
  81. code = [
  82. 'var x = <div>',
  83. ' {this.props.children}',
  84. '</div>;'
  85. ].join('\n');
  86. result = [
  87. 'var x = React.createElement("div", null, ',
  88. ' this.props.children',
  89. ');'
  90. ].join('\n');
  91. expect(transform(code).code).toEqual(result);
  92. // Try with a single interpolated child, wrapped in a composite.
  93. code = [
  94. 'var x = <Composite>',
  95. ' {this.props.children}',
  96. '</Composite>;'
  97. ].join('\n');
  98. result = [
  99. 'var x = React.createElement(Composite, null, ',
  100. ' this.props.children',
  101. ');'
  102. ].join('\n');
  103. expect(transform(code).code).toEqual(result);
  104. // Try with a single composite child, wrapped in a composite.
  105. code = [
  106. 'var x = <Composite>',
  107. ' <Composite2 />',
  108. '</Composite>;'
  109. ].join('\n');
  110. result = [
  111. 'var x = React.createElement(Composite, null, ',
  112. ' React.createElement(Composite2, null)',
  113. ');'
  114. ].join('\n');
  115. expect(transform(code).code).toEqual(result);
  116. });
  117. it('should insert commas after expressions before whitespace', function() {
  118. var code = [
  119. 'var x =',
  120. ' <div',
  121. ' attr1={',
  122. ' "foo" + "bar"',
  123. ' }',
  124. ' attr2={',
  125. ' "foo" + "bar" +',
  126. ' ',
  127. ' "baz" + "bug"',
  128. ' }',
  129. ' attr3={',
  130. ' "foo" + "bar" +',
  131. ' "baz" + "bug"',
  132. ' // Extra line here.',
  133. ' }',
  134. ' attr4="baz">',
  135. ' </div>;'
  136. ].join('\n');
  137. var result = [
  138. 'var x =',
  139. ' React.createElement("div", {',
  140. ' attr1: ',
  141. ' "foo" + "bar", ',
  142. ' ',
  143. ' attr2: ',
  144. ' "foo" + "bar" +',
  145. ' ',
  146. ' "baz" + "bug", ',
  147. ' ',
  148. ' attr3: ',
  149. ' "foo" + "bar" +',
  150. ' "baz" + "bug", ',
  151. ' // Extra line here.',
  152. ' ',
  153. ' attr4: "baz"}',
  154. ' );'
  155. ].join('\n');
  156. expect(transform(code).code).toEqual(result);
  157. });
  158. it('should properly handle comments adjacent to children', function() {
  159. var code = [
  160. 'var x = (',
  161. ' <div>',
  162. ' {/* A comment at the beginning */}',
  163. ' {/* A second comment at the beginning */}',
  164. ' <span>',
  165. ' {/* A nested comment */}',
  166. ' </span>',
  167. ' {/* A sandwiched comment */}',
  168. ' <br />',
  169. ' {/* A comment at the end */}',
  170. ' {/* A second comment at the end */}',
  171. ' </div>',
  172. ');'
  173. ].join('\n');
  174. var result = [
  175. 'var x = (',
  176. ' React.createElement("div", null, ',
  177. ' /* A comment at the beginning */',
  178. ' /* A second comment at the beginning */',
  179. ' React.createElement("span", null',
  180. ' /* A nested comment */',
  181. ' ), ',
  182. ' /* A sandwiched comment */',
  183. ' React.createElement("br", null)',
  184. ' /* A comment at the end */',
  185. ' /* A second comment at the end */',
  186. ' )',
  187. ');'
  188. ].join('\n');
  189. expect(transform(code).code).toBe(result);
  190. });
  191. it('should properly handle comments between props', function() {
  192. var code = [
  193. 'var x = (',
  194. ' <div',
  195. ' /* a multi-line',
  196. ' comment */',
  197. ' attr1="foo">',
  198. ' <span // a double-slash comment',
  199. ' attr2="bar"',
  200. ' />',
  201. ' </div>',
  202. ');'
  203. ].join('\n');
  204. var result = [
  205. 'var x = (',
  206. ' React.createElement("div", {',
  207. ' /* a multi-line',
  208. ' comment */',
  209. ' attr1: "foo"}, ',
  210. ' React.createElement("span", {// a double-slash comment',
  211. ' attr2: "bar"}',
  212. ' )',
  213. ' )',
  214. ');'
  215. ].join('\n');
  216. expect(transform(code).code).toBe(result);
  217. });
  218. it('should not strip tags with a single child of &nbsp;', function() {
  219. var code = [
  220. '<div>&nbsp;</div>;'
  221. ].join('\n');
  222. var result = [
  223. 'React.createElement("div", null, "\u00A0");'
  224. ].join('\n');
  225. expect(transform(code).code).toBe(result);
  226. });
  227. it('should not strip &nbsp; even coupled with other whitespace', function() {
  228. var code = [
  229. '<div>&nbsp; </div>;'
  230. ].join('\n');
  231. var result = [
  232. 'React.createElement("div", null, "\u00A0 ");'
  233. ].join('\n');
  234. expect(transform(code).code).toBe(result);
  235. });
  236. it('should handle hasOwnProperty correctly', function() {
  237. var code = '<hasOwnProperty>testing</hasOwnProperty>;';
  238. var result = 'React.createElement("hasOwnProperty", null, "testing");';
  239. expect(transform(code).code).toBe(result);
  240. });
  241. it('should allow constructor as prop', function() {
  242. var code = '<Component constructor="foo" />;';
  243. var result = 'React.createElement(Component, {constructor: "foo"});';
  244. expect(transform(code).code).toBe(result);
  245. });
  246. it('should allow JS namespacing', function() {
  247. var code = '<Namespace.Component />;';
  248. var result = 'React.createElement(Namespace.Component, null);';
  249. expect(transform(code).code).toBe(result);
  250. });
  251. it('should allow deeper JS namespacing', function() {
  252. var code = '<Namespace.DeepNamespace.Component />;';
  253. var result =
  254. 'React.createElement(Namespace.DeepNamespace.Component, null);';
  255. expect(transform(code).code).toBe(result);
  256. });
  257. it('should disallow XML namespacing', function() {
  258. var code = '<Namespace:Component />;';
  259. expect(() => transform(code)).toThrow();
  260. });
  261. it('wraps props in React.__spread for spread attributes', function() {
  262. var code =
  263. '<Component { ... x } y\n' +
  264. '={2 } z />';
  265. var result =
  266. 'React.createElement(Component, React.__spread({}, x , {y: \n' +
  267. '2, z: true}))';
  268. expect(transform(code).code).toBe(result);
  269. });
  270. it('adds appropriate newlines when using spread attribute', function() {
  271. var code =
  272. '<Component\n' +
  273. ' {...this.props}\n' +
  274. ' sound="moo" />';
  275. var result =
  276. 'React.createElement(Component, React.__spread({}, \n' +
  277. ' this.props, \n' +
  278. ' {sound: "moo"}))';
  279. expect(transform(code).code).toBe(result);
  280. });
  281. it('handles overparenthesized JS', function() {
  282. var code =
  283. '<foo a={(b)} c={(d)}>Foo {(e+f //A line comment\n' +
  284. '/* A multiline comment */)\n' +
  285. '} bar\n' +
  286. '</foo>';
  287. var result = 'React.createElement("foo", {a: (b), c: (d)}, "Foo ", (e+f //A line comment\n' +
  288. '/* A multiline comment */), \n' +
  289. '" bar"\n' +
  290. ')';
  291. expect(transform(code).code).toBe(result);
  292. });
  293. it('should transform known hyphenated tags', function() {
  294. var code = '<font-face />;';
  295. var result = 'React.createElement("font-face", null);';
  296. expect(transform(code).code).toBe(result);
  297. });
  298. it('does not call React.__spread when there are no spreads', function() {
  299. expectObjectAssign(
  300. '<Component x={y} />'
  301. ).not.toBeCalled();
  302. });
  303. it('should not throw for unknown hyphenated tags', function() {
  304. var code = '<x-component />;';
  305. expect(function() {
  306. transform(code);
  307. }).not.toThrow();
  308. });
  309. it('calls assign with a new target object for spreads', function() {
  310. expectObjectAssign(
  311. '<Component {...x} />'
  312. ).toBeCalledWith({}, x);
  313. });
  314. it('calls assign with an empty object when the spread is first', function() {
  315. expectObjectAssign(
  316. '<Component { ...x } y={2} />'
  317. ).toBeCalledWith({}, x, {y: 2});
  318. });
  319. it('coalesces consecutive properties into a single object', function() {
  320. expectObjectAssign(
  321. '<Component { ... x } y={2} z />'
  322. ).toBeCalledWith({}, x, {y: 2, z: true});
  323. });
  324. it('avoids an unnecessary empty object when spread is not first', function() {
  325. expectObjectAssign(
  326. '<Component x={1} {...y} />'
  327. ).toBeCalledWith({x: 1}, y);
  328. });
  329. it('passes the same value multiple times to React.__spread', function() {
  330. expectObjectAssign(
  331. '<Component x={1} y="2" {...z} {...z}><Child /></Component>'
  332. ).toBeCalledWith({x: 1, y: '2'}, z, z);
  333. });
  334. it('evaluates sequences before passing them to React.__spread', function() {
  335. expectObjectAssign(
  336. '<Component x="1" {...(z = { y: 2 }, z)} z={3}>Text</Component>'
  337. ).toBeCalledWith({x: '1'}, {y: 2}, {z: 3});
  338. });
  339. });