Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
}
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