[Fiber] Fix reactComponentExpect (#8258)
* Add more reactComponentExpect tests * Implement reactComponentExpect in Fiber
Dan Abramov committed
Nov 10, 2016 at 21:19 UTC
3e2680411efa3b478afc1f26057f417c7aae7915
5 files changed
+245
-30
scripts/fiber/tests-failing.txt
-3
@@ -599,6 +599,3 @@ src/test/__tests__/ReactTestUtils-test.js
599
* should change the value of an input field in a component
600
* can scry with stateless components involved
601
* should set the type of the event
602
-
603
-src/test/__tests__/reactComponentExpect-test.js
604
-* should detect text components
scripts/fiber/tests-passing.txt
+7
@@ -1262,3 +1262,10 @@ src/test/__tests__/ReactTestUtils-test.js
1262
* can scryRenderedDOMComponentsWithClass with multiple classes
1263
* should throw when attempting to use ReactTestUtils.Simulate with shallow rendering
1264
* should not warn when simulating events with extra properties
1265
+
1266
+src/test/__tests__/reactComponentExpect-test.js
1267
+* should match composite components
1268
+* should match empty DOM components
1269
+* should match non-empty DOM components
1270
+* should match DOM component children
1271
+* should detect text components
src/test/ReactTestUtils.js
+3
@@ -165,6 +165,7 @@ var ReactTestUtils = {
165
return (constructor === type);
166
},
167
168
+ // TODO: deprecate? It's undocumented and unused.
169
isCompositeComponentElement: function(inst) {
170
if (!React.isValidElement(inst)) {
171
return false;
@@ -178,6 +179,7 @@ var ReactTestUtils = {
179
);
180
},
181
182
+ // TODO: deprecate? It's undocumented and unused.
183
isCompositeComponentElementWithType: function(inst, type) {
184
var internalInstance = ReactInstanceMap.get(inst);
185
var constructor = internalInstance
@@ -188,6 +190,7 @@ var ReactTestUtils = {
190
(constructor === type));
191
},
192
193
+ // TODO: deprecate? It's undocumented and unused.
194
getRenderedChildOfCompositeComponent: function(inst) {
195
if (!ReactTestUtils.isCompositeComponent(inst)) {
196
return null;
src/test/__tests__/reactComponentExpect-test.js
+129
-1
@@ -23,6 +23,135 @@ describe('reactComponentExpect', () => {
23
reactComponentExpect = require('reactComponentExpect');
24
});
25
26
+ it('should match composite components', () => {
27
+ class SomeComponent extends React.Component {
28
+ state = {y: 2};
29
+ render() {
30
+ return (
31
+ <div className="Hello" />
32
+ );
33
+ }
34
+ }
35
+
36
+ var component = ReactTestUtils.renderIntoDocument(<SomeComponent x={1} />);
37
+ reactComponentExpect(component)
38
+ .toBePresent()
39
+ .toBeCompositeComponent()
40
+ .toBeComponentOfType(SomeComponent)
41
+ .toBeCompositeComponentWithType(SomeComponent)
42
+ .scalarPropsEqual({x: 1})
43
+ .scalarStateEqual({y: 2});
44
+ });
45
+
46
+ it('should match empty DOM components', () => {
47
+ class SomeComponent extends React.Component {
48
+ render() {
49
+ return (
50
+ <div className="Hello" />
51
+ );
52
+ }
53
+ }
54
+
55
+ var component = ReactTestUtils.renderIntoDocument(<SomeComponent />);
56
+ reactComponentExpect(component)
57
+ .expectRenderedChild()
58
+ .toBePresent()
59
+ .toBeDOMComponent()
60
+ .toBeDOMComponentWithNoChildren()
61
+ .toBeComponentOfType('div');
62
+ });
63
+
64
+ it('should match non-empty DOM components', () => {
65
+ class SomeComponent extends React.Component {
66
+ render() {
67
+ return (
68
+ <div className="Hello">
69
+ <p>1</p>
70
+ <p>2</p>
71
+ </div>
72
+ );
73
+ }
74
+ }
75
+
76
+ var component = ReactTestUtils.renderIntoDocument(<SomeComponent />);
77
+ reactComponentExpect(component)
78
+ .expectRenderedChild()
79
+ .toBePresent()
80
+ .toBeDOMComponent()
81
+ .toBeDOMComponentWithChildCount(2)
82
+ .toBeComponentOfType('div');
83
+ });
84
+
85
+ it('should match DOM component children', () => {
86
+ class Inner extends React.Component {
87
+ render() {
88
+ return <section />;
89
+ }
90
+ }
91
+
92
+ class Noop extends React.Component {
93
+ render() {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ class SomeComponent extends React.Component {
99
+ render() {
100
+ return (
101
+ <div className="Hello">
102
+ <p>1</p>
103
+ <Inner foo="bar" />
104
+ <span>{'Two'}{3}</span>
105
+ <Noop />
106
+ </div>
107
+ );
108
+ }
109
+ }
110
+
111
+ var component = ReactTestUtils.renderIntoDocument(<SomeComponent />);
112
+ reactComponentExpect(component)
113
+ .expectRenderedChild()
114
+ .expectRenderedChildAt(0)
115
+ .toBePresent()
116
+ .toBeDOMComponent()
117
+ .toBeDOMComponentWithNoChildren()
118
+ .toBeComponentOfType('p');
119
+
120
+ reactComponentExpect(component)
121
+ .expectRenderedChild()
122
+ .expectRenderedChildAt(1)
123
+ .toBePresent()
124
+ .toBeCompositeComponentWithType(Inner)
125
+ .scalarPropsEqual({foo: 'bar'})
126
+ .expectRenderedChild()
127
+ .toBeComponentOfType('section')
128
+ .toBeDOMComponentWithNoChildren();
129
+
130
+ reactComponentExpect(component)
131
+ .expectRenderedChild()
132
+ .expectRenderedChildAt(2)
133
+ .toBePresent()
134
+ .toBeDOMComponent()
135
+ .toBeComponentOfType('span')
136
+ .toBeDOMComponentWithChildCount(2)
137
+ .expectRenderedChildAt(0)
138
+ .toBeTextComponentWithValue('Two');
139
+
140
+ reactComponentExpect(component)
141
+ .expectRenderedChild()
142
+ .expectRenderedChildAt(2)
143
+ .expectRenderedChildAt(1)
144
+ .toBeTextComponentWithValue('3');
145
+
146
+ reactComponentExpect(component)
147
+ .expectRenderedChild()
148
+ .expectRenderedChildAt(3)
149
+ .toBePresent()
150
+ .toBeCompositeComponentWithType(Noop)
151
+ .expectRenderedChild()
152
+ .toBeEmptyComponent();
153
+ });
154
+
155
it('should detect text components', () => {
156
class SomeComponent extends React.Component {
157
render() {
@@ -36,7 +165,6 @@ describe('reactComponentExpect', () => {
165
}
166
167
var component = ReactTestUtils.renderIntoDocument(<SomeComponent />);
39
-
168
reactComponentExpect(component)
169
.expectRenderedChild()
170
.expectRenderedChildAt(1)
src/test/reactComponentExpect.js
+106
-26
@@ -13,9 +13,23 @@
13
14
var ReactInstanceMap = require('ReactInstanceMap');
15
var ReactTestUtils = require('ReactTestUtils');
16
+var ReactTypeOfWork = require('ReactTypeOfWork');
17
+
18
+var {
19
+ HostText,
20
+} = ReactTypeOfWork;
21
22
var invariant = require('invariant');
23
24
+// Fiber doesn't actually have an instance for empty components
25
+// but we'll pretend it does while we keep compatibility with Stack.
26
+var fiberNullInstance = {
27
+ type: null,
28
+ child: null,
29
+ sibling: null,
30
+ tag: 99,
31
+};
32
+
33
function reactComponentExpect(instance) {
34
if (instance instanceof reactComponentExpectInternal) {
35
return instance;
@@ -52,7 +66,13 @@ Object.assign(reactComponentExpectInternal.prototype, {
66
* @instance: Retrieves the backing instance.
67
*/
68
instance: function() {
55
- return this._instance.getPublicInstance();
69
+ if (typeof this._instance.tag === 'number') {
70
+ // Fiber reconciler
71
+ return this._instance.stateNode;
72
+ } else {
73
+ // Stack reconciler
74
+ return this._instance.getPublicInstance();
75
+ }
76
},
77
78
/**
@@ -71,7 +91,14 @@ Object.assign(reactComponentExpectInternal.prototype, {
91
*/
92
expectRenderedChild: function() {
93
this.toBeCompositeComponent();
74
- var child = this._instance._renderedComponent;
94
+ var child = null;
95
+ if (typeof this._instance.tag === 'number') {
96
+ // Fiber reconciler
97
+ child = this._instance.child || fiberNullInstance;
98
+ } else {
99
+ // Stack reconciler
100
+ child = this._instance._renderedComponent;
101
+ }
102
// TODO: Hide ReactEmptyComponent instances here?
103
return new reactComponentExpectInternal(child);
104
},
@@ -83,15 +110,30 @@ Object.assign(reactComponentExpectInternal.prototype, {
110
// Currently only dom components have arrays of children, but that will
111
// change soon.
112
this.toBeDOMComponent();
86
- var renderedChildren =
87
- this._instance._renderedChildren || {};
88
- for (var name in renderedChildren) {
89
- if (!renderedChildren.hasOwnProperty(name)) {
90
- continue;
113
+
114
+ if (typeof this._instance.tag === 'number') {
115
+ // Fiber reconciler
116
+ var child = this._instance.child;
117
+ var i = 0;
118
+ while (child) {
119
+ if (i === childIndex) {
120
+ return new reactComponentExpectInternal(child);
121
+ }
122
+ child = child.sibling;
123
+ i++;
124
}
92
- if (renderedChildren[name]) {
93
- if (renderedChildren[name]._mountIndex === childIndex) {
94
- return new reactComponentExpectInternal(renderedChildren[name]);
125
+ } else {
126
+ // Stack reconciler
127
+ var renderedChildren =
128
+ this._instance._renderedChildren || {};
129
+ for (var name in renderedChildren) {
130
+ if (!renderedChildren.hasOwnProperty(name)) {
131
+ continue;
132
+ }
133
+ if (renderedChildren[name]) {
134
+ if (renderedChildren[name]._mountIndex === childIndex) {
135
+ return new reactComponentExpectInternal(renderedChildren[name]);
136
+ }
137
}
138
}
139
}
@@ -100,24 +142,47 @@ Object.assign(reactComponentExpectInternal.prototype, {
142
143
toBeDOMComponentWithChildCount: function(count) {
144
this.toBeDOMComponent();
103
- var renderedChildren = this._instance._renderedChildren;
104
- expect(renderedChildren).toBeTruthy();
105
- expect(Object.keys(renderedChildren).length).toBe(count);
145
+ if (typeof this._instance.tag === 'number') {
146
+ // Fiber reconciler
147
+ var child = this._instance.child;
148
+ var i = 0;
149
+ while (child) {
150
+ child = child.sibling;
151
+ i++;
152
+ }
153
+ expect(i).toBe(count);
154
+ } else {
155
+ // Stack reconciler
156
+ var renderedChildren = this._instance._renderedChildren;
157
+ if (count > 0) {
158
+ expect(renderedChildren).toBeTruthy();
159
+ expect(Object.keys(renderedChildren).length).toBe(count);
160
+ } else {
161
+ expect(renderedChildren).toBeFalsy();
162
+ }
163
+ }
164
return this;
165
},
166
167
toBeDOMComponentWithNoChildren: function() {
110
- this.toBeDOMComponent();
111
- expect(this._instance._renderedChildren).toBeFalsy();
168
+ this.toBeDOMComponentWithChildCount(0);
169
return this;
170
},
171
172
// Matchers ------------------------------------------------------------------
173
174
toBeComponentOfType: function(constructor) {
118
- expect(
119
- this._instance._currentElement.type === constructor
120
- ).toBe(true);
175
+ if (typeof this._instance.tag === 'number') {
176
+ // Fiber reconciler
177
+ expect(
178
+ this._instance.type === constructor
179
+ ).toBe(true);
180
+ } else {
181
+ // Stack reconciler
182
+ expect(
183
+ this._instance._currentElement.type === constructor
184
+ ).toBe(true);
185
+ }
186
return this;
187
},
188
@@ -126,6 +191,8 @@ Object.assign(reactComponentExpectInternal.prototype, {
191
* here.
192
*/
193
toBeCompositeComponent: function() {
194
+ // TODO: this code predates functional components
195
+ // and doesn't work with them.
196
expect(
197
typeof this.instance() === 'object' &&
198
typeof this.instance().render === 'function'
@@ -135,22 +202,35 @@ Object.assign(reactComponentExpectInternal.prototype, {
202
203
toBeCompositeComponentWithType: function(constructor) {
204
this.toBeCompositeComponent();
138
- expect(
139
- this._instance._currentElement.type === constructor
140
- ).toBe(true);
205
+ this.toBeComponentOfType(constructor);
206
return this;
207
},
208
209
toBeTextComponentWithValue: function(val) {
145
- var elementType = typeof this._instance._currentElement;
146
- expect(elementType === 'string' || elementType === 'number').toBe(true);
147
- expect(this._instance._stringText).toBe(val);
210
+ if (typeof this._instance.tag === 'number') {
211
+ // Fiber reconciler
212
+ expect(this._instance.tag === HostText).toBe(true);
213
+ var actualVal = this._instance.memoizedProps;
214
+ expect(typeof actualVal === 'string' || typeof actualVal === 'number').toBe(true);
215
+ expect(String(actualVal)).toBe(val);
216
+ } else {
217
+ // Fiber reconciler
218
+ var elementType = typeof this._instance._currentElement;
219
+ expect(elementType === 'string' || elementType === 'number').toBe(true);
220
+ expect(this._instance._stringText).toBe(val);
221
+ }
222
return this;
223
},
224
225
toBeEmptyComponent: function() {
152
- var element = this._instance._currentElement;
153
- return element === null || element === false;
226
+ if (typeof this._instance.tag === 'number') {
227
+ // Fiber reconciler
228
+ expect(this._instance).toBe(fiberNullInstance);
229
+ } else {
230
+ // Stack reconciler
231
+ var element = this._instance._currentElement;
232
+ expect(element === null || element === false).toBe(true);
233
+ }
234
},
235
236
toBePresent: function() {