Skip to content

Commit

Permalink
[Shallow] Implement callEffects option (facebook#15275)
Browse files Browse the repository at this point in the history
  • Loading branch information
insidewhy committed Jul 21, 2019
1 parent 2c4d61e commit e558ecb
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 6 deletions.
110 changes: 105 additions & 5 deletions packages/react-test-renderer/src/ReactShallowRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ function areHookInputsEqual(
return true;
}

function doEffectInputsDiffer(
before: Array<mixed>,
after: Array<mixed> | void | null,
) {
if (after == null || before.length !== after.length) {
return true;
}

return before.some((value, i) => after[i] !== value);
}

class Updater {
constructor(renderer) {
this._renderer = renderer;
Expand Down Expand Up @@ -172,12 +183,21 @@ function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}

type ShallowRenderOptions = {
callEffects: boolean,
};

const isEffectHook = Symbol();

class ReactShallowRenderer {
static createRenderer = function() {
return new ReactShallowRenderer();
static createRenderer = function(
options: ShallowRenderOptions = {callEffects: false},
) {
return new ReactShallowRenderer(options);
};

constructor() {
constructor(options: ShallowRenderOptions) {
this._options = options;
this._reset();
}

Expand All @@ -199,6 +219,7 @@ class ReactShallowRenderer {
this._numberOfReRenders = 0;
}

_options: ShallowRenderOptions;
_context: null | Object;
_newState: null | Object;
_instance: any;
Expand Down Expand Up @@ -308,6 +329,54 @@ class ReactShallowRenderer {
);
};

const useGenericEffect = (
create: () => (() => void) | void,
inputs: Array<mixed> | void | null,
isLayoutEffect: boolean,
) => {
this._validateCurrentlyRenderingComponent();
this._createWorkInProgressHook();

if (this._workInProgressHook !== null) {
if (this._workInProgressHook.memoizedState == null) {
this._workInProgressHook.memoizedState = {
isEffectHook,
isLayoutEffect,
create,
inputs,
cleanup: null,
run: true,
};
} else {
const {memoizedState} = this._workInProgressHook;
this._workInProgressHook.memoizedState = {
isEffectHook,
isLayoutEffect,
create,
inputs,
cleanup: memoizedState.cleanup,
run:
inputs == null ||
doEffectInputsDiffer(inputs, memoizedState.inputs),
};
}
}
};

const useEffect = (
create: () => (() => void) | void,
inputs: Array<mixed> | void | null,
) => {
useGenericEffect(create, inputs, false);
};

const useLayoutEffect = (
create: () => (() => void) | void,
inputs: Array<mixed> | void | null,
) => {
useGenericEffect(create, inputs, true);
};

const useMemo = <T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
Expand Down Expand Up @@ -374,9 +443,9 @@ class ReactShallowRenderer {
return readContext(context);
},
useDebugValue: noOp,
useEffect: noOp,
useEffect: this._options.callEffects ? useEffect : noOp,
useImperativeHandle: noOp,
useLayoutEffect: noOp,
useLayoutEffect: this._options.callEffects ? useLayoutEffect : noOp,
useMemo,
useReducer,
useRef,
Expand Down Expand Up @@ -619,6 +688,7 @@ class ReactShallowRenderer {
ReactCurrentDispatcher.current = prevDispatcher;
}
this._finishHooks(element, context);
this._callEffectsIfDesired();
}
}
}
Expand Down Expand Up @@ -679,6 +749,36 @@ class ReactShallowRenderer {
// because DOM refs are not available.
}

_callEffectsIfDesired() {
if (!this._options.callEffects) {
return;
}

this._callEffects(true);
this._callEffects(false);
}

_callEffects(callLayoutEffects: boolean) {
for (
let hook = this._firstWorkInProgressHook;
hook !== null;
hook = hook.next
) {
const {memoizedState} = hook;
if (
memoizedState != null &&
memoizedState.isEffectHook === isEffectHook &&
memoizedState.isLayoutEffect === callLayoutEffects &&
memoizedState.run
) {
if (memoizedState.cleanup) {
memoizedState.cleanup();
}
memoizedState.cleanup = memoizedState.create();
}
}
}

_updateClassComponent(
elementType: Function,
element: ReactElement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ describe('ReactShallowRenderer with hooks', () => {
);
});

it('should not trigger effects', () => {
it('should not trigger effects by default', () => {
let effectsCalled = [];

function SomeComponent({defaultName}) {
Expand All @@ -252,6 +252,36 @@ describe('ReactShallowRenderer with hooks', () => {
expect(effectsCalled).toEqual([]);
});

describe('when callEffects option is used', () => {
it('should trigger effects after render', () => {
let happenings = [];

function SomeComponent({defaultName}) {
React.useEffect(() => {
happenings.push('call effect');
});

React.useLayoutEffect(() => {
happenings.push('call layout effect');
});

happenings.push('render');

return <div>Hello world</div>;
}

const shallowRenderer = createRenderer({callEffects: true});
shallowRenderer.render(<SomeComponent />);

// Note the layout effect is triggered first.
expect(happenings).toEqual([
'render',
'call layout effect',
'call effect',
]);
});
});

it('should work with useRef', () => {
function SomeComponent() {
const randomNumberRef = React.useRef({number: Math.random()});
Expand Down

0 comments on commit e558ecb

Please sign in to comment.