Skip to content

Commit

Permalink
[Fiber] Fix reactComponentExpect (#8258)
Browse files Browse the repository at this point in the history
* Add more reactComponentExpect tests

* Implement reactComponentExpect in Fiber
  • Loading branch information
gaearon authored Nov 10, 2016
1 parent 67dcc58 commit 3e26804
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 30 deletions.
3 changes: 0 additions & 3 deletions scripts/fiber/tests-failing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions src/test/ReactTestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -188,6 +190,7 @@ var ReactTestUtils = {
(constructor === type));
},

// TODO: deprecate? It's undocumented and unused.
getRenderedChildOfCompositeComponent: function(inst) {
if (!ReactTestUtils.isCompositeComponent(inst)) {
return null;
Expand Down
130 changes: 129 additions & 1 deletion src/test/__tests__/reactComponentExpect-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,135 @@ describe('reactComponentExpect', () => {
reactComponentExpect = require('reactComponentExpect');
});

it('should match composite components', () => {
class SomeComponent extends React.Component {
state = {y: 2};
render() {
return (
<div className="Hello" />
);
}
}

var component = ReactTestUtils.renderIntoDocument(<SomeComponent x={1} />);
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 (
<div className="Hello" />
);
}
}

var component = ReactTestUtils.renderIntoDocument(<SomeComponent />);
reactComponentExpect(component)
.expectRenderedChild()
.toBePresent()
.toBeDOMComponent()
.toBeDOMComponentWithNoChildren()
.toBeComponentOfType('div');
});

it('should match non-empty DOM components', () => {
class SomeComponent extends React.Component {
render() {
return (
<div className="Hello">
<p>1</p>
<p>2</p>
</div>
);
}
}

var component = ReactTestUtils.renderIntoDocument(<SomeComponent />);
reactComponentExpect(component)
.expectRenderedChild()
.toBePresent()
.toBeDOMComponent()
.toBeDOMComponentWithChildCount(2)
.toBeComponentOfType('div');
});

it('should match DOM component children', () => {
class Inner extends React.Component {
render() {
return <section />;
}
}

class Noop extends React.Component {
render() {
return null;
}
}

class SomeComponent extends React.Component {
render() {
return (
<div className="Hello">
<p>1</p>
<Inner foo="bar" />
<span>{'Two'}{3}</span>
<Noop />
</div>
);
}
}

var component = ReactTestUtils.renderIntoDocument(<SomeComponent />);
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() {
Expand All @@ -36,7 +165,6 @@ describe('reactComponentExpect', () => {
}

var component = ReactTestUtils.renderIntoDocument(<SomeComponent />);

reactComponentExpect(component)
.expectRenderedChild()
.expectRenderedChildAt(1)
Expand Down
132 changes: 106 additions & 26 deletions src/test/reactComponentExpect.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
},

/**
Expand All @@ -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);
},
Expand All @@ -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]);
}
}
}
}
Expand All @@ -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();

This comment has been minimized.

Copy link
@sebmarkbage

sebmarkbage Nov 11, 2016

Collaborator

Why is this expected to be falsy? This is a logic change for Stack. It's breaking tests in www.

This comment has been minimized.

Copy link
@gaearon

gaearon Nov 11, 2016

Author Collaborator

I moved this expectation from toBeDOMComponentWithNoChildren because I assumed toBeDOMComponentWithNoChildren() and toBeDOMComponentWithChildCount(0) are supposed to behave the same way—apparently not?

This comment has been minimized.

Copy link
@gaearon

gaearon Nov 11, 2016

Author Collaborator
}
}
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;
},

Expand All @@ -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'
Expand All @@ -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() {
Expand Down

0 comments on commit 3e26804

Please sign in to comment.