@samitouri / QOS-React / commits / 5cbb68258b

Add (failing) React ART tests

This helps us make sure we don't break React ART in a minor or patch release. The idea is to not change these files when making minor or patch changes. Copied directly from react-art with requires fixed. (I also picked a different haste name just in case.)

Ben Alpert committed May 14, 2016 at 15:30 UTC 5cbb68258bd02dcb4b0f0867a833f5997bdadf03
4 files changed +934
.eslintignore
+1
@@ -1,4 +1,5 @@
1 # We can probably lint these later but not important at this point
2 +src/renderers/art
3 src/shared/vendor
4 # But not in docs/_js/examples/*
5 docs/_js/*.js
package.json
+1
@@ -3,6 +3,7 @@
3 "private": true,
4 "version": "16.0.0-alpha",
5 "devDependencies": {
6 + "art": "^0.10.1",
7 "async": "^1.5.0",
8 "babel-cli": "^6.6.5",
9 "babel-core": "^6.0.0",
src/renderers/art/ReactART.js new
+643
@@ -0,0 +1,643 @@
1 +/**
2 + * Copyright (c) 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 + * @providesModule ReactARTFifteen
10 + */
11 +
12 +'use strict';
13 +
14 +require('art/modes/current').setCurrent(
15 + require('art/modes/fast-noSideEffects') // Flip this to DOM mode for debugging
16 +);
17 +
18 +const Transform = require('art/core/transform');
19 +const Mode = require('art/modes/current');
20 +
21 +const React = require('React');
22 +const ReactDOM = require('ReactDOM');
23 +const ReactInstanceMap = require('ReactInstanceMap');
24 +const ReactMultiChild = require('ReactMultiChild');
25 +const ReactUpdates = require('ReactUpdates');
26 +
27 +const emptyObject = require('emptyObject');
28 +const invariant = require('invariant');
29 +
30 +const assign = require('object-assign');
31 +const pooledTransform = new Transform();
32 +
33 +// Utilities
34 +
35 +function childrenAsString(children) {
36 + if (!children) {
37 + return '';
38 + }
39 + if (typeof children === 'string') {
40 + return children;
41 + }
42 + if (children.length) {
43 + return children.join('\n');
44 + }
45 + return '';
46 +}
47 +
48 +function createComponent(name) {
49 + const ReactARTComponent = function(element) {
50 + this.node = null;
51 + this.subscriptions = null;
52 + this.listeners = null;
53 + this._mountImage = null;
54 + this._renderedChildren = null;
55 + this.construct(element);
56 + };
57 + ReactARTComponent.displayName = name;
58 + for (let i = 1, l = arguments.length; i < l; i++) {
59 + assign(ReactARTComponent.prototype, arguments[i]);
60 + }
61 +
62 + return ReactARTComponent;
63 +}
64 +
65 +/**
66 + * Insert `node` into `parentNode` after `referenceNode`.
67 + */
68 +function injectAfter(parentNode, referenceNode, node) {
69 + let beforeNode;
70 + if (node.parentNode === parentNode &&
71 + node.previousSibling === referenceNode) {
72 + return;
73 + }
74 + if (referenceNode == null) {
75 + // node is supposed to be first.
76 + beforeNode = parentNode.firstChild;
77 + } else {
78 + // node is supposed to be after referenceNode.
79 + beforeNode = referenceNode.nextSibling;
80 + }
81 + if (beforeNode && beforeNode.previousSibling !== node) {
82 + // Cases where `node === beforeNode` should get filtered out by earlier
83 + // checks and the behavior isn't well-defined.
84 + invariant(
85 + node !== beforeNode,
86 + 'ReactART: Can not insert node before itself'
87 + );
88 + node.injectBefore(beforeNode);
89 + } else if (node.parentNode !== parentNode) {
90 + node.inject(parentNode);
91 + }
92 +}
93 +
94 +// ContainerMixin for components that can hold ART nodes
95 +
96 +const ContainerMixin = assign({}, ReactMultiChild.Mixin, {
97 +
98 + /**
99 + * Moves a child component to the supplied index.
100 + *
101 + * @param {ReactComponent} child Component to move.
102 + * @param {number} toIndex Destination index of the element.
103 + * @protected
104 + */
105 + moveChild: function(child, afterNode, toIndex, lastIndex) {
106 + const childNode = child._mountImage;
107 + injectAfter(this.node, afterNode, childNode);
108 + },
109 +
110 + /**
111 + * Creates a child component.
112 + *
113 + * @param {ReactComponent} child Component to create.
114 + * @param {object} childNode ART node to insert.
115 + * @protected
116 + */
117 + createChild: function(child, afterNode, childNode) {
118 + child._mountImage = childNode;
119 + injectAfter(this.node, afterNode, childNode);
120 + },
121 +
122 + /**
123 + * Removes a child component.
124 + *
125 + * @param {ReactComponent} child Child to remove.
126 + * @protected
127 + */
128 + removeChild: function(child) {
129 + child._mountImage.eject();
130 + child._mountImage = null;
131 + },
132 +
133 + updateChildrenAtRoot: function(nextChildren, transaction) {
134 + this.updateChildren(nextChildren, transaction, emptyObject);
135 + },
136 +
137 + mountAndInjectChildrenAtRoot: function(children, transaction) {
138 + this.mountAndInjectChildren(children, transaction, emptyObject);
139 + },
140 +
141 + /**
142 + * Override to bypass batch updating because it is not necessary.
143 + *
144 + * @param {?object} nextChildren.
145 + * @param {ReactReconcileTransaction} transaction
146 + * @internal
147 + * @override {ReactMultiChild.Mixin.updateChildren}
148 + */
149 + updateChildren: function(nextChildren, transaction, context) {
150 + this._updateChildren(nextChildren, transaction, context);
151 + },
152 +
153 + // Shorthands
154 +
155 + mountAndInjectChildren: function(children, transaction, context) {
156 + const mountedImages = this.mountChildren(
157 + children,
158 + transaction,
159 + context
160 + );
161 + // Each mount image corresponds to one of the flattened children
162 + let i = 0;
163 + for (let key in this._renderedChildren) {
164 + if (this._renderedChildren.hasOwnProperty(key)) {
165 + const child = this._renderedChildren[key];
166 + child._mountImage = mountedImages[i];
167 + mountedImages[i].inject(this.node);
168 + i++;
169 + }
170 + }
171 + }
172 +
173 +});
174 +
175 +// Surface is a React DOM Component, not an ART component. It serves as the
176 +// entry point into the ART reconciler.
177 +
178 +const Surface = React.createClass({
179 +
180 + displayName: 'Surface',
181 +
182 + mixins: [ContainerMixin],
183 +
184 + componentDidMount: function() {
185 + const domNode = ReactDOM.findDOMNode(this);
186 +
187 + this.node = Mode.Surface(+this.props.width, +this.props.height, domNode);
188 +
189 + const transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
190 + transaction.perform(
191 + this.mountAndInjectChildren,
192 + this,
193 + this.props.children,
194 + transaction,
195 + ReactInstanceMap.get(this)._context
196 + );
197 + ReactUpdates.ReactReconcileTransaction.release(transaction);
198 + },
199 +
200 + componentDidUpdate: function(oldProps) {
201 + const node = this.node;
202 + if (this.props.width != oldProps.width ||
203 + this.props.height != oldProps.height) {
204 + node.resize(+this.props.width, +this.props.height);
205 + }
206 +
207 + const transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
208 + transaction.perform(
209 + this.updateChildren,
210 + this,
211 + this.props.children,
212 + transaction,
213 + ReactInstanceMap.get(this)._context
214 + );
215 + ReactUpdates.ReactReconcileTransaction.release(transaction);
216 +
217 + if (node.render) {
218 + node.render();
219 + }
220 + },
221 +
222 + componentWillUnmount: function() {
223 + this.unmountChildren();
224 + },
225 +
226 + render: function() {
227 + // This is going to be a placeholder because we don't know what it will
228 + // actually resolve to because ART may render canvas, vml or svg tags here.
229 + // We only allow a subset of properties since others might conflict with
230 + // ART's properties.
231 + const props = this.props;
232 +
233 + // TODO: ART's Canvas Mode overrides surface title and cursor
234 + const Tag = Mode.Surface.tagName;
235 + return (
236 + <Tag
237 + accesskey={props.accesskey}
238 + className={props.className}
239 + draggable={props.draggable}
240 + role={props.role}
241 + style={props.style}
242 + tabindex={props.tabindex}
243 + title={props.title}
244 + />
245 + );
246 + }
247 +
248 +});
249 +
250 +// Various nodes that can go into a surface
251 +
252 +const EventTypes = {
253 + onMouseMove: 'mousemove',
254 + onMouseOver: 'mouseover',
255 + onMouseOut: 'mouseout',
256 + onMouseUp: 'mouseup',
257 + onMouseDown: 'mousedown',
258 + onClick: 'click'
259 +};
260 +
261 +const NodeMixin = {
262 +
263 + construct: function(element) {
264 + this._currentElement = element;
265 + },
266 +
267 + getNativeNode: function() {
268 + return this.node;
269 + },
270 +
271 + getPublicInstance: function() {
272 + return this.node;
273 + },
274 +
275 + putEventListener: function(type, listener) {
276 + const subscriptions = this.subscriptions || (this.subscriptions = {});
277 + const listeners = this.listeners || (this.listeners = {});
278 + listeners[type] = listener;
279 + if (listener) {
280 + if (!subscriptions[type]) {
281 + subscriptions[type] = this.node.subscribe(type, listener, this);
282 + }
283 + } else {
284 + if (subscriptions[type]) {
285 + subscriptions[type]();
286 + delete subscriptions[type];
287 + }
288 + }
289 + },
290 +
291 + handleEvent: function(event) {
292 + const listener = this.listeners[event.type];
293 + if (!listener) {
294 + return;
295 + }
296 + if (typeof listener === 'function') {
297 + listener.call(this, event);
298 + } else if (listener.handleEvent) {
299 + listener.handleEvent(event);
300 + }
301 + },
302 +
303 + destroyEventListeners: function() {
304 + const subscriptions = this.subscriptions;
305 + if (subscriptions) {
306 + for (let type in subscriptions) {
307 + subscriptions[type]();
308 + }
309 + }
310 + this.subscriptions = null;
311 + this.listeners = null;
312 + },
313 +
314 + applyNodeProps: function(oldProps, props) {
315 + const node = this.node;
316 +
317 + const scaleX = props.scaleX != null ? props.scaleX :
318 + props.scale != null ? props.scale : 1;
319 + const scaleY = props.scaleY != null ? props.scaleY :
320 + props.scale != null ? props.scale : 1;
321 +
322 + pooledTransform
323 + .transformTo(1, 0, 0, 1, 0, 0)
324 + .move(props.x || 0, props.y || 0)
325 + .rotate(props.rotation || 0, props.originX, props.originY)
326 + .scale(scaleX, scaleY, props.originX, props.originY);
327 +
328 + if (props.transform != null) {
329 + pooledTransform.transform(props.transform);
330 + }
331 +
332 + if (node.xx !== pooledTransform.xx || node.yx !== pooledTransform.yx ||
333 + node.xy !== pooledTransform.xy || node.yy !== pooledTransform.yy ||
334 + node.x !== pooledTransform.x || node.y !== pooledTransform.y) {
335 + node.transformTo(pooledTransform);
336 + }
337 +
338 + if (props.cursor !== oldProps.cursor || props.title !== oldProps.title) {
339 + node.indicate(props.cursor, props.title);
340 + }
341 +
342 + if (node.blend && props.opacity !== oldProps.opacity) {
343 + node.blend(props.opacity == null ? 1 : props.opacity);
344 + }
345 +
346 + if (props.visible !== oldProps.visible) {
347 + if (props.visible == null || props.visible) {
348 + node.show();
349 + } else {
350 + node.hide();
351 + }
352 + }
353 +
354 + for (let type in EventTypes) {
355 + this.putEventListener(EventTypes[type], props[type]);
356 + }
357 + },
358 +
359 + mountComponentIntoNode: function(rootID, container) {
360 + throw new Error(
361 + 'You cannot render an ART component standalone. ' +
362 + 'You need to wrap it in a Surface.'
363 + );
364 + }
365 +
366 +};
367 +
368 +// Group
369 +
370 +const Group = createComponent('Group', NodeMixin, ContainerMixin, {
371 +
372 + mountComponent: function(
373 + transaction,
374 + nativeParent,
375 + nativeContainerInfo,
376 + context
377 + ) {
378 + this.node = Mode.Group();
379 + const props = this._currentElement.props;
380 + this.applyGroupProps(emptyObject, props);
381 + this.mountAndInjectChildren(props.children, transaction, context);
382 + return this.node;
383 + },
384 +
385 + receiveComponent: function(nextComponent, transaction, context) {
386 + const props = nextComponent.props;
387 + const oldProps = this._currentElement.props;
388 + this.applyGroupProps(oldProps, props);
389 + this.updateChildren(props.children, transaction, context);
390 + this._currentElement = nextComponent;
391 + },
392 +
393 + applyGroupProps: function(oldProps, props) {
394 + this.node.width = props.width;
395 + this.node.height = props.height;
396 + this.applyNodeProps(oldProps, props);
397 + },
398 +
399 + unmountComponent: function() {
400 + this.destroyEventListeners();
401 + this.unmountChildren();
402 + }
403 +
404 +});
405 +
406 +// ClippingRectangle
407 +const ClippingRectangle = createComponent(
408 + 'ClippingRectangle', NodeMixin, ContainerMixin, {
409 +
410 + mountComponent: function(
411 + transaction,
412 + nativeParent,
413 + nativeContainerInfo,
414 + context
415 + ) {
416 + this.node = Mode.ClippingRectangle();
417 + const props = this._currentElement.props;
418 + this.applyClippingProps(emptyObject, props);
419 + this.mountAndInjectChildren(props.children, transaction, context);
420 + return this.node;
421 + },
422 +
423 + receiveComponent: function(nextComponent, transaction, context) {
424 + const props = nextComponent.props;
425 + const oldProps = this._currentElement.props;
426 + this.applyClippingProps(oldProps, props);
427 + this.updateChildren(props.children, transaction, context);
428 + this._currentElement = nextComponent;
429 + },
430 +
431 + applyClippingProps: function(oldProps, props) {
432 + this.node.width = props.width;
433 + this.node.height = props.height;
434 + this.node.x = props.x;
435 + this.node.y = props.y;
436 + this.applyNodeProps(oldProps, props);
437 + },
438 +
439 + unmountComponent: function() {
440 + this.destroyEventListeners();
441 + this.unmountChildren();
442 + }
443 +
444 +});
445 +
446 +
447 +// Renderables
448 +
449 +const RenderableMixin = assign({}, NodeMixin, {
450 +
451 + applyRenderableProps: function(oldProps, props) {
452 + if (oldProps.fill !== props.fill) {
453 + if (props.fill && props.fill.applyFill) {
454 + props.fill.applyFill(this.node);
455 + } else {
456 + this.node.fill(props.fill);
457 + }
458 + }
459 + if (
460 + oldProps.stroke !== props.stroke ||
461 + oldProps.strokeWidth !== props.strokeWidth ||
462 + oldProps.strokeCap !== props.strokeCap ||
463 + oldProps.strokeJoin !== props.strokeJoin ||
464 + // TODO: Consider a deep check of stokeDash.
465 + // This may benefit the VML version in IE.
466 + oldProps.strokeDash !== props.strokeDash
467 + ) {
468 + this.node.stroke(
469 + props.stroke,
470 + props.strokeWidth,
471 + props.strokeCap,
472 + props.strokeJoin,
473 + props.strokeDash
474 + );
475 + }
476 + this.applyNodeProps(oldProps, props);
477 + },
478 +
479 + unmountComponent: function() {
480 + this.destroyEventListeners();
481 + }
482 +
483 +});
484 +
485 +// Shape
486 +
487 +const Shape = createComponent('Shape', RenderableMixin, {
488 +
489 + construct: function(element) {
490 + this._currentElement = element;
491 + this._oldDelta = null;
492 + this._oldPath = null;
493 + },
494 +
495 + mountComponent: function(
496 + transaction,
497 + nativeParent,
498 + nativeContainerInfo,
499 + context
500 + ) {
501 + this.node = Mode.Shape();
502 + const props = this._currentElement.props;
503 + this.applyShapeProps(emptyObject, props);
504 + return this.node;
505 + },
506 +
507 + receiveComponent: function(nextComponent, transaction, context) {
508 + const props = nextComponent.props;
509 + const oldProps = this._currentElement.props;
510 + this.applyShapeProps(oldProps, props);
511 + this._currentElement = nextComponent;
512 + },
513 +
514 + applyShapeProps: function(oldProps, props) {
515 + const oldDelta = this._oldDelta;
516 + const oldPath = this._oldPath;
517 + const path = props.d || childrenAsString(props.children);
518 +
519 + if (path.delta !== oldDelta ||
520 + path !== oldPath ||
521 + oldProps.width !== props.width ||
522 + oldProps.height !== props.height) {
523 +
524 + this.node.draw(
525 + path,
526 + props.width,
527 + props.height
528 + );
529 +
530 + this._oldPath = path;
531 + this._oldDelta = path.delta;
532 + }
533 +
534 + this.applyRenderableProps(oldProps, props);
535 + }
536 +
537 +});
538 +
539 +// Text
540 +
541 +const Text = createComponent('Text', RenderableMixin, {
542 +
543 + construct: function(element) {
544 + this._currentElement = element;
545 + this._oldString = null;
546 + },
547 +
548 + mountComponent: function(
549 + transaction,
550 + nativeParent,
551 + nativeContainerInfo,
552 + context
553 + ) {
554 + const props = this._currentElement.props;
555 + const newString = childrenAsString(props.children);
556 + this.node = Mode.Text(newString, props.font, props.alignment, props.path);
557 + this._oldString = newString;
558 + this.applyRenderableProps(emptyObject, props);
559 + return this.node;
560 + },
561 +
562 + isSameFont: function(oldFont, newFont) {
563 + if (oldFont === newFont) {
564 + return true;
565 + }
566 + if (typeof newFont === 'string' || typeof oldFont === 'string') {
567 + return false;
568 + }
569 + return (
570 + newFont.fontSize === oldFont.fontSize &&
571 + newFont.fontStyle === oldFont.fontStyle &&
572 + newFont.fontVariant === oldFont.fontVariant &&
573 + newFont.fontWeight === oldFont.fontWeight &&
574 + newFont.fontFamily === oldFont.fontFamily
575 + );
576 + },
577 +
578 + receiveComponent: function(nextComponent, transaction, context) {
579 + const props = nextComponent.props;
580 + const oldProps = this._currentElement.props;
581 +
582 + const oldString = this._oldString;
583 + const newString = childrenAsString(props.children);
584 +
585 + if (oldString !== newString ||
586 + !this.isSameFont(oldProps.font, props.font) ||
587 + oldProps.alignment !== props.alignment ||
588 + oldProps.path !== props.path) {
589 + this.node.draw(
590 + newString,
591 + props.font,
592 + props.alignment,
593 + props.path
594 + );
595 + this._oldString = newString;
596 + }
597 +
598 + this.applyRenderableProps(oldProps, props);
599 + this._currentElement = nextComponent;
600 + }
601 +
602 +});
603 +
604 +// Declarative fill type objects - API design not finalized
605 +
606 +const slice = Array.prototype.slice;
607 +
608 +function LinearGradient(stops, x1, y1, x2, y2) {
609 + this.args = slice.call(arguments);
610 +}
611 +
612 +LinearGradient.prototype.applyFill = function(node) {
613 + node.fillLinear.apply(node, this.args);
614 +};
615 +
616 +function RadialGradient(stops, fx, fy, rx, ry, cx, cy) {
617 + this.args = slice.call(arguments);
618 +}
619 +
620 +RadialGradient.prototype.applyFill = function(node) {
621 + node.fillRadial.apply(node, this.args);
622 +};
623 +
624 +function Pattern(url, width, height, left, top) {
625 + this.args = slice.call(arguments);
626 +}
627 +
628 +Pattern.prototype.applyFill = function(node) {
629 + node.fillImage.apply(node, this.args);
630 +};
631 +
632 +module.exports = {
633 + ClippingRectangle,
634 + Group,
635 + LinearGradient,
636 + Path: Mode.Path,
637 + Pattern,
638 + RadialGradient,
639 + Shape,
640 + Surface,
641 + Text,
642 + Transform,
643 +};
src/renderers/art/__tests__/ReactART-test.js new
+289
@@ -0,0 +1,289 @@
1 +/**
2 + * Copyright (c) 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 +
12 +/*jslint evil: true */
13 +
14 +'use strict';
15 +
16 +jest
17 + .unmock('ReactARTFifteen');
18 +
19 +var React = require('React');
20 +var ReactDOM = require('ReactDOM');
21 +var ReactTestUtils = require('ReactTestUtils');
22 +
23 +var Group;
24 +var Shape;
25 +var Surface;
26 +var TestComponent;
27 +
28 +var Missing = {};
29 +
30 +var ReactART = require('ReactARTFifteen');
31 +var ARTSVGMode = require('art/modes/svg');
32 +var ARTCurrentMode = require('art/modes/current');
33 +
34 +function testDOMNodeStructure(domNode, expectedStructure) {
35 + expect(domNode).toBeDefined();
36 + expect(domNode.nodeName).toBe(expectedStructure.nodeName);
37 + for (var prop in expectedStructure) {
38 + if (!expectedStructure.hasOwnProperty(prop)) continue;
39 + if (prop != 'nodeName' && prop != 'children') {
40 + if (expectedStructure[prop] === Missing) {
41 + expect(domNode.hasAttribute(prop)).toBe(false);
42 + } else {
43 + expect(domNode.getAttribute(prop)).toBe(expectedStructure[prop]);
44 + }
45 + }
46 + }
47 + if (expectedStructure.children) {
48 + expectedStructure.children.forEach(function(subTree, index) {
49 + testDOMNodeStructure(domNode.childNodes[index], subTree);
50 + });
51 + }
52 +}
53 +
54 +describe('ReactART', function() {
55 +
56 + beforeEach(function() {
57 + ARTCurrentMode.setCurrent(ARTSVGMode);
58 +
59 + Group = ReactART.Group;
60 + Shape = ReactART.Shape;
61 + Surface = ReactART.Surface;
62 +
63 + TestComponent = React.createClass({
64 + render: function() {
65 +
66 + var a =
67 + <Shape
68 + d="M0,0l50,0l0,50l-50,0z"
69 + fill={new ReactART.LinearGradient(["black", "white"])}
70 + key="a"
71 + width={50} height={50}
72 + x={50} y={50}
73 + opacity={0.1}
74 + />;
75 +
76 + var b =
77 + <Shape
78 + fill="#3C5A99"
79 + key="b"
80 + scale={0.5}
81 + x={50} y={50}
82 + title="This is an F"
83 + cursor="pointer">
84 + M64.564,38.583H54l0.008-5.834c0-3.035,0.293-4.666,4.657-4.666
85 + h5.833V16.429h-9.33c-11.213,0-15.159,5.654-15.159,15.16v6.994
86 + h-6.99v11.652h6.99v33.815H54V50.235h9.331L64.564,38.583z
87 + </Shape>;
88 +
89 + var c = <Group key="c" />;
90 +
91 + return (
92 + <Surface width={150} height={200}>
93 + <Group ref="group">
94 + {this.props.flipped ? [b, a, c] : [a, b, c]}
95 + </Group>
96 + </Surface>
97 + );
98 + }
99 + });
100 + });
101 +
102 + it('should have the correct lifecycle state', function() {
103 + var instance = <TestComponent />;
104 + instance = ReactTestUtils.renderIntoDocument(instance);
105 + var group = instance.refs.group;
106 + // Duck type test for an ART group
107 + expect(typeof group.indicate).toBe('function');
108 + });
109 +
110 + it('should render a reasonable SVG structure in SVG mode', function() {
111 + var instance = <TestComponent />;
112 + instance = ReactTestUtils.renderIntoDocument(instance);
113 +
114 + var expectedStructure = {
115 + nodeName: 'svg',
116 + width: '150',
117 + height: '200',
118 + children: [
119 + { nodeName: 'defs' },
120 + {
121 + nodeName: 'g',
122 + children: [
123 + {
124 + nodeName: 'defs',
125 + children: [
126 + { nodeName: 'linearGradient' }
127 + ]
128 + },
129 + { nodeName: 'path' },
130 + { nodeName: 'path' },
131 + { nodeName: 'g' }
132 + ]
133 + }
134 + ]
135 + };
136 +
137 + var realNode = ReactDOM.findDOMNode(instance);
138 + testDOMNodeStructure(realNode, expectedStructure);
139 + });
140 +
141 + it('should be able to reorder components', function() {
142 + var container = document.createElement('div');
143 + var instance = ReactDOM.render(<TestComponent flipped={false} />, container);
144 +
145 + var expectedStructure = {
146 + nodeName: 'svg',
147 + children: [
148 + { nodeName: 'defs' },
149 + {
150 + nodeName: 'g',
151 + children: [
152 + { nodeName: 'defs' },
153 + { nodeName: 'path', opacity: '0.1' },
154 + { nodeName: 'path', opacity: Missing },
155 + { nodeName: 'g' }
156 + ]
157 + }
158 + ]
159 + };
160 +
161 + var realNode = ReactDOM.findDOMNode(instance);
162 + testDOMNodeStructure(realNode, expectedStructure);
163 +
164 + ReactDOM.render(<TestComponent flipped={true} />, container);
165 +
166 + var expectedNewStructure = {
167 + nodeName: 'svg',
168 + children: [
169 + { nodeName: 'defs' },
170 + {
171 + nodeName: 'g',
172 + children: [
173 + { nodeName: 'defs' },
174 + { nodeName: 'path', opacity: Missing },
175 + { nodeName: 'path', opacity: '0.1' },
176 + { nodeName: 'g' }
177 + ]
178 + }
179 + ]
180 + };
181 +
182 + testDOMNodeStructure(realNode, expectedNewStructure);
183 + });
184 +
185 + it('should be able to reorder many components', function() {
186 + var container = document.createElement('div');
187 +
188 + var Component = React.createClass({
189 + render: function() {
190 + var chars = this.props.chars.split('');
191 + return (
192 + <Surface>
193 + {chars.map((text) => <Shape key={text} title={text} />)}
194 + </Surface>
195 + );
196 + },
197 + });
198 +
199 + // Mini multi-child stress test: lots of reorders, some adds, some removes.
200 + var before = 'abcdefghijklmnopqrst';
201 + var after = 'mxhpgwfralkeoivcstzy';
202 +
203 + var instance = ReactDOM.render(<Component chars={before} />, container);
204 + var realNode = ReactDOM.findDOMNode(instance);
205 + expect(realNode.textContent).toBe(before);
206 +
207 + instance = ReactDOM.render(<Component chars={after} />, container);
208 + expect(realNode.textContent).toBe(after);
209 +
210 + ReactDOM.unmountComponentAtNode(container);
211 + });
212 +
213 + it('renders composite with lifecycle inside group', function() {
214 + var mounted = false;
215 + var CustomShape = React.createClass({
216 + render: function() {
217 + return <Shape />;
218 + },
219 + componentDidMount: function() {
220 + mounted = true;
221 + }
222 + });
223 + ReactTestUtils.renderIntoDocument(
224 + <Surface>
225 + <Group>
226 + <CustomShape />
227 + </Group>
228 + </Surface>
229 + );
230 + expect(mounted).toBe(true);
231 + });
232 +
233 + it('resolves refs before componentDidMount', function() {
234 + var CustomShape = React.createClass({
235 + render: function() {
236 + return <Shape />;
237 + }
238 + });
239 + var ref = null;
240 + var Outer = React.createClass({
241 + componentDidMount: function() {
242 + ref = this.refs.test;
243 + },
244 + render: function() {
245 + return (
246 + <Surface>
247 + <Group>
248 + <CustomShape ref="test" />
249 + </Group>
250 + </Surface>
251 + );
252 + }
253 + });
254 + ReactTestUtils.renderIntoDocument(<Outer />);
255 + expect(ref.constructor).toBe(CustomShape);
256 + });
257 +
258 + it('resolves refs before componentDidUpdate', function() {
259 + var CustomShape = React.createClass({
260 + render: function() {
261 + return <Shape />;
262 + }
263 + });
264 + var ref = {};
265 + var Outer = React.createClass({
266 + componentDidMount: function() {
267 + ref = this.refs.test;
268 + },
269 + componentDidUpdate: function() {
270 + ref = this.refs.test;
271 + },
272 + render: function() {
273 + return (
274 + <Surface>
275 + <Group>
276 + {this.props.mountCustomShape && <CustomShape ref="test" />}
277 + </Group>
278 + </Surface>
279 + );
280 + }
281 + });
282 + var container = document.createElement('div');
283 + ReactDOM.render(<Outer />, container);
284 + expect(ref).not.toBeDefined();
285 + ReactDOM.render(<Outer mountCustomShape={true} />, container);
286 + expect(ref.constructor).toBe(CustomShape);
287 + });
288 +
289 +});