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 ( +
+

1

+

2

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

1

+ + {'Two'}{3} + +
+ ); + } + } + + 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() {