Skip to content

Commit

Permalink
fix: upgrade tests
Browse files Browse the repository at this point in the history
  • Loading branch information
marcosmoura committed Aug 25, 2023
1 parent 006fc84 commit 03a806a
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,37 @@ const renderHookWithRef = (

Object.entries(style).forEach(([key, value]) => value && refEl.style.setProperty(key, value));

act(() => {
if (typeof hook.result.current.ref === 'function') {
hook.result.current.ref(refEl);
}
});
function renderRef() {
act(() => {
if (!hook.result.current.canRender) {
return;
}

if (typeof hook.result.current.ref === 'function') {
hook.result.current.ref(refEl);
}
});
}

act(() => renderRef());

function rerender(motion: MotionShorthand, options?: MotionOptions) {
hook.rerender({ motion, options });
act(() => renderRef());
}

return { ...hook, rerender };
};

return hook;
const jumpAnimationFrame = () => {
// requestAnimationFrame
act(() => jest.advanceTimersToNextTimer());
};

const jumpToNextFrame = () => {
const jumpAnimationTimeout = (timeout: number = defaultDuration) => {
// timeout + requestAnimationFrame
act(() => {
// requestAnimationFrame + timeout callbacks
jest.advanceTimersToNextTimer();
jest.advanceTimersByTime(timeout);
jest.advanceTimersToNextTimer();
});
};
Expand All @@ -45,13 +63,55 @@ describe('useMotion', () => {
jest.resetAllMocks();
});

describe('when motion is received', () => {
it('should return default values when presence is false', () => {
const defaultState = getDefaultMotionState();
const { result } = renderHookWithRef(getDefaultMotionState());

expect(result.current.type).toStrictEqual('unmounted');
expect(result.current.ref).toStrictEqual(defaultState.ref);
expect(result.current.active).toStrictEqual(false);
});

it('should return default values when presence is true', () => {
const defaultState = getDefaultMotionState();
const { result } = renderHookWithRef({ ...getDefaultMotionState(), active: true });

expect(result.current.ref).toStrictEqual(defaultState.ref);
expect(result.current.active).toStrictEqual(true);
});

it('should show error when motion changes to a different type', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => ({}));
let defaultMotion: MotionShorthand = getDefaultMotionState();
const { rerender } = renderHook(() => useIsMotion(defaultMotion));

defaultMotion = false;

rerender();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(
[
'useMotion: The hook needs to be called with the same typeof of shorthand on every render.',
'This is to ensure the internal state of the hook is stable and can be used to accurately detect the motion state.',
'Please make sure to not change the shorthand on subsequent renders or to use the hook conditionally.',
'\nCurrent shorthand:',
JSON.stringify(defaultMotion, null, 2),
'\nPrevious shorthand:',
JSON.stringify(getDefaultMotionState(), null, 2),
].join(' '),
);
});
});

describe('when presence is false by default', () => {
it('should return default values when presence is false', () => {
const { result } = renderHookWithRef(false);

expect(typeof result.current.ref).toBe('function');
expect(result.current.type).toBe('unmounted');
expect(result.current.active).toBe(false);
expect(result.current.canRender).toBe(false);
});
});

Expand All @@ -61,15 +121,18 @@ describe('useMotion', () => {

expect(typeof result.current.ref).toBe('function');
expect(result.current.active).toBe(true);
expect(result.current.type).not.toBe('unmounted');
expect(result.current.canRender).toBe(true);
});

it('should change visible to true when animateOnFirstMount is true', () => {
const { result } = renderHookWithRef(true, { animateOnFirstMount: true });

expect(typeof result.current.ref).toBe('function');
expect(result.current.active).toBe(false);
expect(result.current.canRender).toBe(true);

jumpToNextFrame();
act(() => jest.advanceTimersToNextTimer());

expect(result.current.active).toBe(true);
});
Expand All @@ -79,56 +142,63 @@ describe('useMotion', () => {
it('should toggle values starting with false', () => {
const { result, rerender } = renderHookWithRef(false);

act(() => jest.advanceTimersToNextTimer());

expect(typeof result.current.ref).toBe('function');
expect(result.current.type).toBe('unmounted');
expect(result.current.active).toBe(false);
expect(result.current.canRender).toBe(false);

rerender({ motion: true });

act(() => jest.advanceTimersToNextTimer());
rerender(true);

expect(result.current.canRender).toBe(true);
expect(result.current.type).toBe('entering');
jumpAnimationFrame();

expect(result.current.active).toBe(true);

act(() => jest.advanceTimersByTime(defaultDuration + 1));
jumpAnimationTimeout();
expect(result.current.type).toBe('entered');

act(() => jest.advanceTimersToNextTimer());

jumpAnimationFrame();
expect(result.current.type).toBe('idle');

rerender({ motion: false });
rerender(false);

act(() => jest.advanceTimersToNextTimer());
expect(result.current.type).toBe('exiting');

jumpAnimationFrame();
expect(result.current.active).toBe(false);

act(() => jest.advanceTimersByTime(defaultDuration + 1));
jumpAnimationTimeout();
expect(result.current.type).toBe('exited');

act(() => jest.advanceTimersToNextTimer());
jumpAnimationFrame();
expect(result.current.type).toBe('unmounted');
expect(result.current.active).toBe(false);
expect(result.current.canRender).toBe(false);
});

it('should toggle values starting with true', () => {
const { result, rerender } = renderHookWithRef(true);

expect(typeof result.current.ref).toBe('function');
expect(result.current.active).toBe(true);
expect(result.current.canRender).toBe(true);

rerender({ motion: false });

act(() => jest.advanceTimersToNextTimer());
rerender(false);

expect(result.current.type).toBe('exiting');

jumpAnimationFrame();
expect(result.current.active).toBe(false);

act(() => jest.advanceTimersByTime(defaultDuration + 1));
jumpAnimationTimeout();
expect(result.current.type).toBe('exited');

act(() => jest.advanceTimersToNextTimer());
jumpAnimationFrame();
expect(result.current.type).toBe('unmounted');
expect(result.current.canRender).toBe(false);
expect(result.current.active).toBe(false);
});
});
Expand All @@ -145,33 +215,32 @@ describe('useMotion', () => {
expect(typeof result.current.ref).toBe('function');
expect(result.current.type).toBe('unmounted');
expect(result.current.active).toBe(false);
expect(result.current.canRender).toBe(false);

rerender({ motion: true });

act(() => jest.advanceTimersToNextTimer());
rerender(true);

expect(result.current.type).toBe('entering');
expect(result.current.active).toBe(true);

act(() => jest.advanceTimersToNextTimer());
jumpAnimationFrame();
expect(result.current.active).toBe(true);

jumpAnimationTimeout();
expect(result.current.type).toBe('entered');

act(() => jest.advanceTimersToNextTimer());

jumpAnimationFrame();
expect(result.current.type).toBe('idle');

rerender({ motion: false });

act(() => jest.advanceTimersToNextTimer());
rerender(false);

expect(result.current.type).toBe('exiting');

jumpAnimationFrame();
expect(result.current.active).toBe(false);

act(() => jest.advanceTimersToNextTimer());
jumpAnimationTimeout();
expect(result.current.type).toBe('exited');

act(() => jest.advanceTimersToNextTimer());
jumpAnimationFrame();
expect(result.current.type).toBe('unmounted');
expect(result.current.active).toBe(false);
});
Expand All @@ -181,80 +250,40 @@ describe('useMotion', () => {
{ message: 'with no transition', styles: { 'transition-duration': '0' } },
{ message: 'with no animation', styles: { 'animation-duration': '0' } },
])('when presence changes - $message', ({ styles }) => {
it('should toggle values when transition-duration is 0', () => {
it('should toggle values starting with false', () => {
const { result, rerender } = renderHookWithRef(false, {}, styles);

expect(typeof result.current.ref).toBe('function');
expect(result.current.type).toBe('unmounted');
expect(result.current.active).toBe(false);
expect(result.current.canRender).toBe(false);

rerender({ motion: true });

act(() => jest.advanceTimersToNextTimer());
expect(result.current.type).toBe('entered');
rerender(true);

act(() => jest.advanceTimersToNextTimer());
expect(result.current.type).toBe('idle');
expect(result.current.type).toBe('entering');

// requestAnimationFrame
act(() => jest.advanceTimersToNextTimer());
jumpAnimationFrame();
expect(result.current.active).toBe(true);
// timeout
act(() => jest.advanceTimersToNextTimer());
expect(result.current.type).toBe('idle');

rerender({ motion: false });
jumpAnimationTimeout(0);
expect(result.current.type).toBe('entered');

act(() => jest.advanceTimersToNextTimer());
act(() => jest.advanceTimersToNextTimer());
expect(result.current.active).toBe(false);
jumpAnimationFrame();
expect(result.current.type).toBe('idle');

// requestAnimationFrame
act(() => jest.advanceTimersToNextTimer());
// timeout
act(() => jest.advanceTimersToNextTimer());
expect(result.current.type).toBe('unmounted');
});
});
rerender(false);

describe('when motion is received', () => {
it('should return default values when presence is false', () => {
const defaultState = getDefaultMotionState();
const { result } = renderHookWithRef(getDefaultMotionState());
expect(result.current.type).toBe('exiting');

expect(result.current.type).toStrictEqual('unmounted');
expect(result.current.ref).toStrictEqual(defaultState.ref);
expect(result.current.active).toStrictEqual(false);
});
jumpAnimationFrame();
expect(result.current.active).toBe(false);

it('should return default values when presence is true', () => {
const defaultState = getDefaultMotionState();
const { result } = renderHookWithRef({ ...getDefaultMotionState(), active: true });
jumpAnimationTimeout(0);
expect(result.current.type).toBe('exited');

expect(result.current.ref).toStrictEqual(defaultState.ref);
expect(result.current.active).toStrictEqual(true);
jumpAnimationFrame();
expect(result.current.type).toBe('unmounted');
expect(result.current.active).toBe(false);
});
});

it('should show error when motion changes to a different type', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => ({}));
let defaultMotion: MotionShorthand = getDefaultMotionState();
const { rerender } = renderHook(() => useIsMotion(defaultMotion));

defaultMotion = false;

rerender();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(
[
'useMotion: The hook needs to be called with the same typeof of shorthand on every render.',
'This is to ensure the internal state of the hook is stable and can be used to accurately detect the motion state.',
'Please make sure to not change the shorthand on subsequent renders or to use the hook conditionally.',
'\nCurrent shorthand:',
JSON.stringify(defaultMotion, null, 2),
'\nPrevious shorthand:',
JSON.stringify(getDefaultMotionState(), null, 2),
].join(' '),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ function useMotionPresence<Element extends HTMLElement>(
setCurrentElement(node);
}, []);

const onFinished = React.useCallback(() => {
setType(presence ? 'entered' : 'exited');
setAnimationFrame(() => setType(presence ? 'idle' : 'unmounted'));
}, [presence, setAnimationFrame]);

React.useEffect(() => {
if (isFirstReactRender) {
return;
Expand Down Expand Up @@ -141,7 +146,7 @@ function useMotionPresence<Element extends HTMLElement>(
const duration = getMotionDuration(currentElement);

if (duration === 0) {
setType(presence ? 'idle' : 'unmounted');
onFinished();
return;
}

Expand All @@ -150,10 +155,7 @@ function useMotionPresence<Element extends HTMLElement>(
* This is an alternative to using the `transitionend` event which can be unreliable as it fires multiple times
* if the transition has multiple properties.
*/
setAnimationTimeout(() => {
setType(presence ? 'entered' : 'exited');
setAnimationFrame(() => setType(presence ? 'idle' : 'unmounted'));
}, duration);
setAnimationTimeout(() => onFinished(), duration);
});
});

Expand All @@ -167,6 +169,7 @@ function useMotionPresence<Element extends HTMLElement>(
currentElement,
disableAnimation,
isFirstReactRender,
onFinished,
presence,
setAnimationFrame,
setAnimationTimeout,
Expand Down

0 comments on commit 03a806a

Please sign in to comment.