diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt
index a41b7230a1eca..6aef29f3858dc 100644
--- a/scripts/fiber/tests-failing.txt
+++ b/scripts/fiber/tests-failing.txt
@@ -599,6 +599,3 @@ src/test/__tests__/ReactTestUtils-test.js
* should change the value of an input field in a component
* can scry with stateless components involved
* should set the type of the event
-
-src/test/__tests__/reactComponentExpect-test.js
-* should detect text components
diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt
index b554584d0163e..933742f6a92f1 100644
--- a/scripts/fiber/tests-passing.txt
+++ b/scripts/fiber/tests-passing.txt
@@ -1262,3 +1262,10 @@ src/test/__tests__/ReactTestUtils-test.js
* can scryRenderedDOMComponentsWithClass with multiple classes
* should throw when attempting to use ReactTestUtils.Simulate with shallow rendering
* should not warn when simulating events with extra properties
+
+src/test/__tests__/reactComponentExpect-test.js
+* should match composite components
+* should match empty DOM components
+* should match non-empty DOM components
+* should match DOM component children
+* should detect text components
diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js
index a10f7acfccf4b..aacd65fa70210 100644
--- a/src/test/ReactTestUtils.js
+++ b/src/test/ReactTestUtils.js
@@ -165,6 +165,7 @@ var ReactTestUtils = {
return (constructor === type);
},
+ // TODO: deprecate? It's undocumented and unused.
isCompositeComponentElement: function(inst) {
if (!React.isValidElement(inst)) {
return false;
@@ -178,6 +179,7 @@ var ReactTestUtils = {
);
},
+ // TODO: deprecate? It's undocumented and unused.
isCompositeComponentElementWithType: function(inst, type) {
var internalInstance = ReactInstanceMap.get(inst);
var constructor = internalInstance
@@ -188,6 +190,7 @@ var ReactTestUtils = {
(constructor === type));
},
+ // TODO: deprecate? It's undocumented and unused.
getRenderedChildOfCompositeComponent: function(inst) {
if (!ReactTestUtils.isCompositeComponent(inst)) {
return null;
diff --git a/src/test/__tests__/reactComponentExpect-test.js b/src/test/__tests__/reactComponentExpect-test.js
index c9646f9ab2764..9fc433af11d7e 100644
--- a/src/test/__tests__/reactComponentExpect-test.js
+++ b/src/test/__tests__/reactComponentExpect-test.js
@@ -23,6 +23,135 @@ describe('reactComponentExpect', () => {
reactComponentExpect = require('reactComponentExpect');
});
+ it('should match composite components', () => {
+ class SomeComponent extends React.Component {
+ state = {y: 2};
+ render() {
+ return (
+
+ );
+ }
+ }
+
+ var component = ReactTestUtils.renderIntoDocument();
+ reactComponentExpect(component)
+ .toBePresent()
+ .toBeCompositeComponent()
+ .toBeComponentOfType(SomeComponent)
+ .toBeCompositeComponentWithType(SomeComponent)
+ .scalarPropsEqual({x: 1})
+ .scalarStateEqual({y: 2});
+ });
+
+ it('should match empty DOM components', () => {
+ class SomeComponent extends React.Component {
+ render() {
+ return (
+
+ );
+ }
+ }
+
+ var component = ReactTestUtils.renderIntoDocument();
+ reactComponentExpect(component)
+ .expectRenderedChild()
+ .toBePresent()
+ .toBeDOMComponent()
+ .toBeDOMComponentWithNoChildren()
+ .toBeComponentOfType('div');
+ });
+
+ it('should match non-empty DOM components', () => {
+ class SomeComponent extends React.Component {
+ render() {
+ return (
+
+ );
+ }
+ }
+
+ var component = ReactTestUtils.renderIntoDocument();
+ reactComponentExpect(component)
+ .expectRenderedChild()
+ .toBePresent()
+ .toBeDOMComponent()
+ .toBeDOMComponentWithChildCount(2)
+ .toBeComponentOfType('div');
+ });
+
+ it('should match DOM component children', () => {
+ class Inner extends React.Component {
+ render() {
+ return ;
+ }
+ }
+
+ class Noop extends React.Component {
+ render() {
+ return null;
+ }
+ }
+
+ class SomeComponent extends React.Component {
+ render() {
+ return (
+
+ );
+ }
+ }
+
+ var component = ReactTestUtils.renderIntoDocument();
+ reactComponentExpect(component)
+ .expectRenderedChild()
+ .expectRenderedChildAt(0)
+ .toBePresent()
+ .toBeDOMComponent()
+ .toBeDOMComponentWithNoChildren()
+ .toBeComponentOfType('p');
+
+ reactComponentExpect(component)
+ .expectRenderedChild()
+ .expectRenderedChildAt(1)
+ .toBePresent()
+ .toBeCompositeComponentWithType(Inner)
+ .scalarPropsEqual({foo: 'bar'})
+ .expectRenderedChild()
+ .toBeComponentOfType('section')
+ .toBeDOMComponentWithNoChildren();
+
+ reactComponentExpect(component)
+ .expectRenderedChild()
+ .expectRenderedChildAt(2)
+ .toBePresent()
+ .toBeDOMComponent()
+ .toBeComponentOfType('span')
+ .toBeDOMComponentWithChildCount(2)
+ .expectRenderedChildAt(0)
+ .toBeTextComponentWithValue('Two');
+
+ reactComponentExpect(component)
+ .expectRenderedChild()
+ .expectRenderedChildAt(2)
+ .expectRenderedChildAt(1)
+ .toBeTextComponentWithValue('3');
+
+ reactComponentExpect(component)
+ .expectRenderedChild()
+ .expectRenderedChildAt(3)
+ .toBePresent()
+ .toBeCompositeComponentWithType(Noop)
+ .expectRenderedChild()
+ .toBeEmptyComponent();
+ });
+
it('should detect text components', () => {
class SomeComponent extends React.Component {
render() {
@@ -36,7 +165,6 @@ describe('reactComponentExpect', () => {
}
var component = ReactTestUtils.renderIntoDocument();
-
reactComponentExpect(component)
.expectRenderedChild()
.expectRenderedChildAt(1)
diff --git a/src/test/reactComponentExpect.js b/src/test/reactComponentExpect.js
index f917ebb786646..1959bea86c564 100644
--- a/src/test/reactComponentExpect.js
+++ b/src/test/reactComponentExpect.js
@@ -13,9 +13,23 @@
var ReactInstanceMap = require('ReactInstanceMap');
var ReactTestUtils = require('ReactTestUtils');
+var ReactTypeOfWork = require('ReactTypeOfWork');
+
+var {
+ HostText,
+} = ReactTypeOfWork;
var invariant = require('invariant');
+// Fiber doesn't actually have an instance for empty components
+// but we'll pretend it does while we keep compatibility with Stack.
+var fiberNullInstance = {
+ type: null,
+ child: null,
+ sibling: null,
+ tag: 99,
+};
+
function reactComponentExpect(instance) {
if (instance instanceof reactComponentExpectInternal) {
return instance;
@@ -52,7 +66,13 @@ Object.assign(reactComponentExpectInternal.prototype, {
* @instance: Retrieves the backing instance.
*/
instance: function() {
- return this._instance.getPublicInstance();
+ if (typeof this._instance.tag === 'number') {
+ // Fiber reconciler
+ return this._instance.stateNode;
+ } else {
+ // Stack reconciler
+ return this._instance.getPublicInstance();
+ }
},
/**
@@ -71,7 +91,14 @@ Object.assign(reactComponentExpectInternal.prototype, {
*/
expectRenderedChild: function() {
this.toBeCompositeComponent();
- var child = this._instance._renderedComponent;
+ var child = null;
+ if (typeof this._instance.tag === 'number') {
+ // Fiber reconciler
+ child = this._instance.child || fiberNullInstance;
+ } else {
+ // Stack reconciler
+ child = this._instance._renderedComponent;
+ }
// TODO: Hide ReactEmptyComponent instances here?
return new reactComponentExpectInternal(child);
},
@@ -83,15 +110,30 @@ Object.assign(reactComponentExpectInternal.prototype, {
// Currently only dom components have arrays of children, but that will
// change soon.
this.toBeDOMComponent();
- var renderedChildren =
- this._instance._renderedChildren || {};
- for (var name in renderedChildren) {
- if (!renderedChildren.hasOwnProperty(name)) {
- continue;
+
+ if (typeof this._instance.tag === 'number') {
+ // Fiber reconciler
+ var child = this._instance.child;
+ var i = 0;
+ while (child) {
+ if (i === childIndex) {
+ return new reactComponentExpectInternal(child);
+ }
+ child = child.sibling;
+ i++;
}
- if (renderedChildren[name]) {
- if (renderedChildren[name]._mountIndex === childIndex) {
- return new reactComponentExpectInternal(renderedChildren[name]);
+ } else {
+ // Stack reconciler
+ var renderedChildren =
+ this._instance._renderedChildren || {};
+ for (var name in renderedChildren) {
+ if (!renderedChildren.hasOwnProperty(name)) {
+ continue;
+ }
+ if (renderedChildren[name]) {
+ if (renderedChildren[name]._mountIndex === childIndex) {
+ return new reactComponentExpectInternal(renderedChildren[name]);
+ }
}
}
}
@@ -100,24 +142,47 @@ Object.assign(reactComponentExpectInternal.prototype, {
toBeDOMComponentWithChildCount: function(count) {
this.toBeDOMComponent();
- var renderedChildren = this._instance._renderedChildren;
- expect(renderedChildren).toBeTruthy();
- expect(Object.keys(renderedChildren).length).toBe(count);
+ if (typeof this._instance.tag === 'number') {
+ // Fiber reconciler
+ var child = this._instance.child;
+ var i = 0;
+ while (child) {
+ child = child.sibling;
+ i++;
+ }
+ expect(i).toBe(count);
+ } else {
+ // Stack reconciler
+ var renderedChildren = this._instance._renderedChildren;
+ if (count > 0) {
+ expect(renderedChildren).toBeTruthy();
+ expect(Object.keys(renderedChildren).length).toBe(count);
+ } else {
+ expect(renderedChildren).toBeFalsy();
+ }
+ }
return this;
},
toBeDOMComponentWithNoChildren: function() {
- this.toBeDOMComponent();
- expect(this._instance._renderedChildren).toBeFalsy();
+ this.toBeDOMComponentWithChildCount(0);
return this;
},
// Matchers ------------------------------------------------------------------
toBeComponentOfType: function(constructor) {
- expect(
- this._instance._currentElement.type === constructor
- ).toBe(true);
+ if (typeof this._instance.tag === 'number') {
+ // Fiber reconciler
+ expect(
+ this._instance.type === constructor
+ ).toBe(true);
+ } else {
+ // Stack reconciler
+ expect(
+ this._instance._currentElement.type === constructor
+ ).toBe(true);
+ }
return this;
},
@@ -126,6 +191,8 @@ Object.assign(reactComponentExpectInternal.prototype, {
* here.
*/
toBeCompositeComponent: function() {
+ // TODO: this code predates functional components
+ // and doesn't work with them.
expect(
typeof this.instance() === 'object' &&
typeof this.instance().render === 'function'
@@ -135,22 +202,35 @@ Object.assign(reactComponentExpectInternal.prototype, {
toBeCompositeComponentWithType: function(constructor) {
this.toBeCompositeComponent();
- expect(
- this._instance._currentElement.type === constructor
- ).toBe(true);
+ this.toBeComponentOfType(constructor);
return this;
},
toBeTextComponentWithValue: function(val) {
- var elementType = typeof this._instance._currentElement;
- expect(elementType === 'string' || elementType === 'number').toBe(true);
- expect(this._instance._stringText).toBe(val);
+ if (typeof this._instance.tag === 'number') {
+ // Fiber reconciler
+ expect(this._instance.tag === HostText).toBe(true);
+ var actualVal = this._instance.memoizedProps;
+ expect(typeof actualVal === 'string' || typeof actualVal === 'number').toBe(true);
+ expect(String(actualVal)).toBe(val);
+ } else {
+ // Fiber reconciler
+ var elementType = typeof this._instance._currentElement;
+ expect(elementType === 'string' || elementType === 'number').toBe(true);
+ expect(this._instance._stringText).toBe(val);
+ }
return this;
},
toBeEmptyComponent: function() {
- var element = this._instance._currentElement;
- return element === null || element === false;
+ if (typeof this._instance.tag === 'number') {
+ // Fiber reconciler
+ expect(this._instance).toBe(fiberNullInstance);
+ } else {
+ // Stack reconciler
+ var element = this._instance._currentElement;
+ expect(element === null || element === false).toBe(true);
+ }
},
toBePresent: function() {