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
+});