= {
+ action,
+ next: null,
+ };
+
+ // Append the update to the end of the list.
+ let last = queue.first;
+ if (last === null) {
+ queue.first = update;
+ } else {
+ while (last.next !== null) {
+ last = last.next;
+ }
+ last.next = update;
+ }
+
+ // Re-render now.
+ this.render(this._element, this._context);
}
}
@@ -441,17 +459,6 @@ class ReactShallowRenderer {
return this._workInProgressHook;
}
- _prepareToUseHooks(componentIdentity: Object): void {
- if (
- this._previousComponentIdentity !== null &&
- this._previousComponentIdentity !== componentIdentity
- ) {
- this._firstWorkInProgressHook = null;
- }
- this._currentlyRenderingComponent = componentIdentity;
- this._previousComponentIdentity = componentIdentity;
- }
-
_finishHooks(element: ReactElement, context: null | Object) {
if (this._didScheduleRenderPhaseUpdate) {
// Updates were scheduled during the render phase. They are stored in
@@ -466,7 +473,6 @@ class ReactShallowRenderer {
this._rendering = false;
this.render(element, context);
} else {
- this._currentlyRenderingComponent = null;
this._workInProgressHook = null;
this._renderPhaseUpdates = null;
this._numberOfReRenders = 0;
@@ -514,6 +520,9 @@ class ReactShallowRenderer {
if (this._rendering) {
return;
}
+ if (this._element != null && this._element.type !== element.type) {
+ this._reset();
+ }
const elementType = isMemo(element.type) ? element.type.type : element.type;
const previousElement = this._element;
@@ -574,11 +583,7 @@ class ReactShallowRenderer {
this._mountClassComponent(elementType, element, this._context);
} else {
let shouldRender = true;
- if (
- isMemo(element.type) &&
- elementType === this._previousComponentIdentity &&
- previousElement !== null
- ) {
+ if (isMemo(element.type) && previousElement !== null) {
// This is a Memo component that is being re-rendered.
const compare = element.type.compare || shallowEqual;
if (compare(previousElement.props, element.props)) {
@@ -588,7 +593,6 @@ class ReactShallowRenderer {
if (shouldRender) {
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = this._dispatcher;
- this._prepareToUseHooks(elementType);
try {
// elementType could still be a ForwardRef if it was
// nested inside Memo.
@@ -626,14 +630,7 @@ class ReactShallowRenderer {
this._instance.componentWillUnmount();
}
}
-
- this._firstWorkInProgressHook = null;
- this._previousComponentIdentity = null;
- this._context = null;
- this._element = null;
- this._newState = null;
- this._rendered = null;
- this._instance = null;
+ this._reset();
}
_mountClassComponent(
diff --git a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
index df7a08d9d5c0d..4332a60a028f7 100644
--- a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
+++ b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
@@ -1565,4 +1565,46 @@ describe('ReactShallowRenderer', () => {
'forwardRef requires a render function but was given object.',
);
});
+
+ it('should let you change type', () => {
+ function Foo({prop}) {
+ return Foo {prop}
;
+ }
+ function Bar({prop}) {
+ return Bar {prop}
;
+ }
+
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render();
+ expect(shallowRenderer.getRenderOutput()).toEqual(Foo {'foo1'}
);
+ shallowRenderer.render();
+ expect(shallowRenderer.getRenderOutput()).toEqual(Foo {'foo2'}
);
+ shallowRenderer.render();
+ expect(shallowRenderer.getRenderOutput()).toEqual(Bar {'bar1'}
);
+ shallowRenderer.render();
+ expect(shallowRenderer.getRenderOutput()).toEqual(Bar {'bar2'}
);
+ });
+
+ it('should let you change class type', () => {
+ class Foo extends React.Component {
+ render() {
+ return Foo {this.props.prop}
;
+ }
+ }
+ class Bar extends React.Component {
+ render() {
+ return Bar {this.props.prop}
;
+ }
+ }
+
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render();
+ expect(shallowRenderer.getRenderOutput()).toEqual(Foo {'foo1'}
);
+ shallowRenderer.render();
+ expect(shallowRenderer.getRenderOutput()).toEqual(Foo {'foo2'}
);
+ shallowRenderer.render();
+ expect(shallowRenderer.getRenderOutput()).toEqual(Bar {'bar1'}
);
+ shallowRenderer.render();
+ expect(shallowRenderer.getRenderOutput()).toEqual(Bar {'bar2'}
);
+ });
});
diff --git a/packages/react-test-renderer/src/__tests__/ReactShallowRendererHooks-test.js b/packages/react-test-renderer/src/__tests__/ReactShallowRendererHooks-test.js
index 29fa94433db2c..6b8a962ad5b14 100644
--- a/packages/react-test-renderer/src/__tests__/ReactShallowRendererHooks-test.js
+++ b/packages/react-test-renderer/src/__tests__/ReactShallowRendererHooks-test.js
@@ -90,6 +90,61 @@ describe('ReactShallowRenderer with hooks', () => {
);
});
+ it('should work with updating a derived value from useState', () => {
+ let _updateName;
+
+ function SomeComponent({defaultName}) {
+ const [name, updateName] = React.useState(defaultName);
+ const [prevName, updatePrevName] = React.useState(defaultName);
+ const [letter, updateLetter] = React.useState(name[0]);
+
+ _updateName = updateName;
+
+ if (name !== prevName) {
+ updatePrevName(name);
+ updateLetter(name[0]);
+ }
+
+ return (
+
+
+ Your name is: {name + ' (' + letter + ')'}
+
+
+ );
+ }
+
+ const shallowRenderer = createRenderer();
+ let result = shallowRenderer.render(
+ ,
+ );
+ expect(result).toEqual(
+
+
+ Your name is: Sophie (S)
+
+
,
+ );
+
+ result = shallowRenderer.render();
+ expect(result).toEqual(
+
+
+ Your name is: Sophie (S)
+
+
,
+ );
+
+ _updateName('Dan');
+ expect(shallowRenderer.getRenderOutput()).toEqual(
+
+
+ Your name is: Dan (D)
+
+
,
+ );
+ });
+
it('should work with useReducer', () => {
function reducer(state, action) {
switch (action.type) {
@@ -322,4 +377,115 @@ describe('ReactShallowRenderer with hooks', () => {
expect(firstResult).toEqual(secondResult);
});
+
+ it('should update a value from useState outside the render', () => {
+ let _dispatch;
+
+ function SomeComponent({defaultName}) {
+ const [count, dispatch] = React.useReducer(
+ (s, a) => (a === 'inc' ? s + 1 : s),
+ 0,
+ );
+ const [name, updateName] = React.useState(defaultName);
+ _dispatch = () => dispatch('inc');
+
+ return (
+ updateName('Dan')}>
+
+ Your name is: {name} ({count})
+
+
+ );
+ }
+
+ const shallowRenderer = createRenderer();
+ const element = ;
+ const result = shallowRenderer.render(element);
+ expect(result.props.children).toEqual(
+
+ Your name is: Dominic ({0})
+
,
+ );
+
+ result.props.onClick();
+ let updated = shallowRenderer.render(element);
+ expect(updated.props.children).toEqual(
+
+ Your name is: Dan ({0})
+
,
+ );
+
+ _dispatch('foo');
+ updated = shallowRenderer.render(element);
+ expect(updated.props.children).toEqual(
+
+ Your name is: Dan ({1})
+
,
+ );
+
+ _dispatch('inc');
+ updated = shallowRenderer.render(element);
+ expect(updated.props.children).toEqual(
+
+ Your name is: Dan ({2})
+
,
+ );
+ });
+
+ it('should ignore a foreign update outside the render', () => {
+ let _updateCountForFirstRender;
+
+ function SomeComponent() {
+ const [count, updateCount] = React.useState(0);
+ if (!_updateCountForFirstRender) {
+ _updateCountForFirstRender = updateCount;
+ }
+ return count;
+ }
+
+ const shallowRenderer = createRenderer();
+ const element = ;
+ let result = shallowRenderer.render(element);
+ expect(result).toEqual(0);
+ _updateCountForFirstRender(1);
+ result = shallowRenderer.render(element);
+ expect(result).toEqual(1);
+
+ shallowRenderer.unmount();
+ result = shallowRenderer.render(element);
+ expect(result).toEqual(0);
+ _updateCountForFirstRender(1); // Should be ignored.
+ result = shallowRenderer.render(element);
+ expect(result).toEqual(0);
+ });
+
+ it('should not forget render phase updates', () => {
+ let _updateCount;
+
+ function SomeComponent() {
+ const [count, updateCount] = React.useState(0);
+ _updateCount = updateCount;
+ if (count < 5) {
+ updateCount(x => x + 1);
+ }
+ return count;
+ }
+
+ const shallowRenderer = createRenderer();
+ const element = ;
+ let result = shallowRenderer.render(element);
+ expect(result).toEqual(5);
+
+ _updateCount(10);
+ result = shallowRenderer.render(element);
+ expect(result).toEqual(10);
+
+ _updateCount(x => x + 1);
+ result = shallowRenderer.render(element);
+ expect(result).toEqual(11);
+
+ _updateCount(x => x - 10);
+ result = shallowRenderer.render(element);
+ expect(result).toEqual(5);
+ });
});