mirror of
https://github.com/streamich/react-use.git
synced 2026-01-18 14:06:52 +00:00
fix: correct useSpring() hook behaviour
Add tests for "Animation" block.
This commit is contained in:
commit
d7a622d7cf
@ -1,6 +1,6 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import { useSpring } from '..';
|
||||
import useSpring from '../useSpring';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const Demo = () => {
|
||||
|
||||
@ -100,8 +100,7 @@ it('should log an error if set with a patch different than an object', () => {
|
||||
const [, set] = result.current;
|
||||
expect(mockConsoleError).not.toHaveBeenCalled();
|
||||
|
||||
// @ts-ignore
|
||||
act(() => set('not an object'));
|
||||
act(() => set('not an object' as any));
|
||||
|
||||
expect(mockConsoleError).toHaveBeenCalledTimes(1);
|
||||
expect(mockConsoleError).toHaveBeenCalledWith('useGetSetState setter patch must be an object.');
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import useMap from '../useMap';
|
||||
|
||||
const setUp = (initialMap?: object) => renderHook(() => useMap(initialMap));
|
||||
const setUp = <T extends object>(initialMap?: T) => renderHook(() => useMap(initialMap));
|
||||
|
||||
it('should init map and utils', () => {
|
||||
const { result } = setUp({ foo: 'bar', a: 1 });
|
||||
@ -28,7 +28,6 @@ it('should get corresponding value for existing provided key', () => {
|
||||
|
||||
let value;
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
value = utils.get('a');
|
||||
});
|
||||
|
||||
@ -36,12 +35,11 @@ it('should get corresponding value for existing provided key', () => {
|
||||
});
|
||||
|
||||
it('should get undefined for non-existing provided key', () => {
|
||||
const { result } = setUp({ foo: 'bar', a: 1 });
|
||||
const { result } = setUp<{ foo: string; a: number; nonExisting?: any }>({ foo: 'bar', a: 1 });
|
||||
const [, utils] = result.current;
|
||||
|
||||
let value;
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
value = utils.get('nonExisting');
|
||||
});
|
||||
|
||||
@ -49,11 +47,10 @@ it('should get undefined for non-existing provided key', () => {
|
||||
});
|
||||
|
||||
it('should set new key-value pair', () => {
|
||||
const { result } = setUp({ foo: 'bar', a: 1 });
|
||||
const { result } = setUp<{ foo: string; a: number; newKey?: number }>({ foo: 'bar', a: 1 });
|
||||
const [, utils] = result.current;
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
utils.set('newKey', 99);
|
||||
});
|
||||
|
||||
@ -65,11 +62,10 @@ it('should override current value if setting existing key', () => {
|
||||
const [, utils] = result.current;
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
utils.set('foo', 99);
|
||||
utils.set('foo', 'qux');
|
||||
});
|
||||
|
||||
expect(result.current[0]).toEqual({ foo: 99, a: 1 });
|
||||
expect(result.current[0]).toEqual({ foo: 'qux', a: 1 });
|
||||
});
|
||||
|
||||
it('should remove corresponding key-value pair for existing provided key', () => {
|
||||
@ -77,7 +73,6 @@ it('should remove corresponding key-value pair for existing provided key', () =>
|
||||
const [, utils] = result.current;
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
utils.remove('foo');
|
||||
});
|
||||
|
||||
@ -85,11 +80,10 @@ it('should remove corresponding key-value pair for existing provided key', () =>
|
||||
});
|
||||
|
||||
it('should do nothing if removing non-existing provided key', () => {
|
||||
const { result } = setUp({ foo: 'bar', a: 1 });
|
||||
const { result } = setUp<{ foo: string; a: number; nonExisting?: any }>({ foo: 'bar', a: 1 });
|
||||
const [, utils] = result.current;
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
utils.remove('nonExisting');
|
||||
});
|
||||
|
||||
@ -97,11 +91,10 @@ it('should do nothing if removing non-existing provided key', () => {
|
||||
});
|
||||
|
||||
it('should reset map to initial object provided', () => {
|
||||
const { result } = setUp({ foo: 'bar', a: 1 });
|
||||
const { result } = setUp<{ foo: string; a: number; z?: number }>({ foo: 'bar', a: 1 });
|
||||
const [, utils] = result.current;
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
utils.set('z', 99);
|
||||
});
|
||||
|
||||
|
||||
153
src/__tests__/useRaf.test.ts
Normal file
153
src/__tests__/useRaf.test.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { replaceRaf } from 'raf-stub';
|
||||
import useRaf from '../useRaf';
|
||||
|
||||
/**
|
||||
* New requestAnimationFrame after being replaced with raf-stub for testing purposes.
|
||||
*/
|
||||
interface RequestAnimationFrame {
|
||||
reset(): void;
|
||||
step(): void;
|
||||
}
|
||||
declare var requestAnimationFrame: RequestAnimationFrame;
|
||||
|
||||
replaceRaf();
|
||||
const fixedStart = 1564949709496;
|
||||
const spyDateNow = jest.spyOn(Date, 'now').mockImplementation(() => fixedStart);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
requestAnimationFrame.reset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
requestAnimationFrame.reset();
|
||||
});
|
||||
|
||||
it('should init percentage of time elapsed', () => {
|
||||
const { result } = renderHook(() => useRaf());
|
||||
const timeElapsed = result.current;
|
||||
|
||||
expect(timeElapsed).toBe(0);
|
||||
});
|
||||
|
||||
it('should return corresponding percentage of time elapsed for default ms', () => {
|
||||
const { result } = renderHook(() => useRaf());
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers(); // start after delay
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12 * 0.25); // 25%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.25);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12 * 0.5); // 50%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.5);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12 * 0.75); // 75%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.75);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12); // 100%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(1);
|
||||
});
|
||||
|
||||
it('should return corresponding percentage of time elapsed for custom ms', () => {
|
||||
const customMs = 2000;
|
||||
|
||||
const { result } = renderHook(() => useRaf(customMs));
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers(); // start after delay
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + customMs * 0.25); // 25%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.25);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + customMs * 0.5); // 50%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.5);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + customMs * 0.75); // 75%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(0.75);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + customMs); // 100%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(1);
|
||||
});
|
||||
|
||||
it('should return always 1 after corresponding ms reached', () => {
|
||||
const { result } = renderHook(() => useRaf());
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers(); // start after delay
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12); // 100%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(1);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12 * 1.1); // 110%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(1);
|
||||
|
||||
act(() => {
|
||||
spyDateNow.mockImplementationOnce(() => fixedStart + 1e12 * 3); // 300%
|
||||
requestAnimationFrame.step();
|
||||
});
|
||||
expect(result.current).toBe(1);
|
||||
});
|
||||
|
||||
it('should wait until delay reached to start calculating elapsed percentage', () => {
|
||||
const { result } = renderHook(() => useRaf(undefined, 500));
|
||||
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(250); // fast-forward only half of custom delay
|
||||
});
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(249); // fast-forward 1ms less than custom delay
|
||||
});
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1); // fast-forward exactly to custom delay
|
||||
});
|
||||
expect(result.current).not.toBe(0);
|
||||
});
|
||||
|
||||
it('should clear pending timers on unmount', () => {
|
||||
const spyRafStop = jest.spyOn(global, 'cancelAnimationFrame' as any);
|
||||
const { unmount } = renderHook(() => useRaf());
|
||||
|
||||
expect(clearTimeout).not.toHaveBeenCalled();
|
||||
expect(spyRafStop).not.toHaveBeenCalled();
|
||||
|
||||
unmount();
|
||||
|
||||
expect(clearTimeout).toHaveBeenCalledTimes(2);
|
||||
expect(spyRafStop).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
119
src/__tests__/useSpring.test.ts
Normal file
119
src/__tests__/useSpring.test.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import useSpring from '../useSpring';
|
||||
import { Spring } from 'rebound';
|
||||
|
||||
// simulate Spring for testing
|
||||
const mockSetCurrentValue = jest.fn();
|
||||
const mockAddListener = jest.fn();
|
||||
const mockSetEndValue = jest.fn();
|
||||
const mockRemoveListener = jest.fn();
|
||||
let triggerSpringUpdate = () => {};
|
||||
let springListener: Listener = { onSpringUpdate: () => {} };
|
||||
|
||||
interface Listener {
|
||||
onSpringUpdate: (currentSpring: Spring) => void;
|
||||
}
|
||||
|
||||
const mockCreateSpring: Spring = jest.fn().mockImplementation(() => {
|
||||
let currentValue = 0;
|
||||
let endValue = 0;
|
||||
|
||||
const getCloserValue = (a, b) => Math.round((a + b) / 2);
|
||||
|
||||
const getCurrentValue = () => {
|
||||
currentValue = getCloserValue(currentValue, endValue);
|
||||
return currentValue;
|
||||
};
|
||||
|
||||
triggerSpringUpdate = () => {
|
||||
if (currentValue !== endValue) {
|
||||
springListener.onSpringUpdate({ getCurrentValue } as any);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
setCurrentValue: val => {
|
||||
currentValue = val;
|
||||
mockSetCurrentValue(val);
|
||||
},
|
||||
addListener: newListener => {
|
||||
springListener = newListener;
|
||||
mockAddListener(newListener);
|
||||
},
|
||||
setEndValue: val => {
|
||||
endValue = val;
|
||||
mockSetEndValue(val);
|
||||
},
|
||||
removeListener: mockRemoveListener,
|
||||
};
|
||||
}) as any;
|
||||
|
||||
jest.mock('rebound', () => {
|
||||
return {
|
||||
Sprint: {},
|
||||
SpringSystem: jest.fn().mockImplementation(() => {
|
||||
return { createSpring: mockCreateSpring };
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
it('should init value to provided target', () => {
|
||||
const { result } = renderHook(() => useSpring(70));
|
||||
|
||||
expect(result.current).toBe(70);
|
||||
expect(mockSetCurrentValue).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetCurrentValue).toHaveBeenCalledWith(70);
|
||||
expect(mockCreateSpring).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateSpring).toHaveBeenCalledWith(50, 3);
|
||||
});
|
||||
|
||||
it('should create spring with custom tension and friction args provided', () => {
|
||||
renderHook(() => useSpring(500, 20, 7));
|
||||
|
||||
expect(mockCreateSpring).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateSpring).toHaveBeenCalledWith(20, 7);
|
||||
});
|
||||
|
||||
it('should subscribe only once', () => {
|
||||
const { rerender } = renderHook(() => useSpring());
|
||||
|
||||
expect(mockAddListener).toHaveBeenCalledTimes(1);
|
||||
expect(mockAddListener).toHaveBeenCalledWith(springListener);
|
||||
|
||||
rerender();
|
||||
|
||||
expect(mockAddListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle spring update', () => {
|
||||
let targetValue = 70;
|
||||
let lastSpringValue = targetValue;
|
||||
const { result, rerender } = renderHook(() => useSpring(targetValue));
|
||||
|
||||
targetValue = 100;
|
||||
rerender();
|
||||
expect(result.current).toBe(lastSpringValue);
|
||||
|
||||
act(() => {
|
||||
triggerSpringUpdate(); // simulate new spring value
|
||||
});
|
||||
expect(result.current).toBeGreaterThan(lastSpringValue);
|
||||
expect(result.current).toBeLessThanOrEqual(targetValue);
|
||||
|
||||
lastSpringValue = result.current;
|
||||
act(() => {
|
||||
triggerSpringUpdate(); // simulate another new spring value
|
||||
});
|
||||
expect(result.current).toBeGreaterThan(lastSpringValue);
|
||||
expect(result.current).toBeLessThanOrEqual(targetValue);
|
||||
});
|
||||
|
||||
it('should remove listener on unmount', () => {
|
||||
const { unmount } = renderHook(() => useSpring());
|
||||
expect(mockRemoveListener).not.toHaveBeenCalled();
|
||||
|
||||
unmount();
|
||||
|
||||
expect(mockRemoveListener).toHaveBeenCalledTimes(1);
|
||||
expect(mockRemoveListener).toHaveBeenCalledWith(springListener);
|
||||
});
|
||||
@ -2,137 +2,135 @@ import { act, renderHook, RenderHookResult } from '@testing-library/react-hooks'
|
||||
import { useTimeout } from '../index';
|
||||
import { UseTimeoutReturn } from '../useTimeout';
|
||||
|
||||
describe('useTimeout', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(useTimeout).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return three functions', () => {
|
||||
const hook = renderHook(() => useTimeout(5));
|
||||
|
||||
expect(hook.result.current.length).toBe(3);
|
||||
expect(typeof hook.result.current[0]).toBe('function');
|
||||
expect(typeof hook.result.current[1]).toBe('function');
|
||||
expect(typeof hook.result.current[2]).toBe('function');
|
||||
});
|
||||
|
||||
function getHook(ms: number = 5): [jest.Mock, RenderHookResult<{ delay: number }, UseTimeoutReturn>] {
|
||||
const spy = jest.fn();
|
||||
return [
|
||||
spy,
|
||||
renderHook(
|
||||
({ delay = 5 }) => {
|
||||
spy();
|
||||
return useTimeout(delay);
|
||||
},
|
||||
{ initialProps: { delay: ms } }
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
it('should re-render component after given amount of time', done => {
|
||||
const [spy, hook] = getHook();
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
});
|
||||
|
||||
it('should cancel timeout on unmount', () => {
|
||||
const [spy, hook] = getHook();
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
hook.unmount();
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('first function should return actual state of timeout', done => {
|
||||
let [, hook] = getHook();
|
||||
let [isReady] = hook.result.current;
|
||||
|
||||
expect(isReady()).toBe(false);
|
||||
hook.unmount();
|
||||
expect(isReady()).toBe(null);
|
||||
|
||||
[, hook] = getHook();
|
||||
[isReady] = hook.result.current;
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(isReady()).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
});
|
||||
|
||||
it('second function should cancel timeout', () => {
|
||||
const [spy, hook] = getHook();
|
||||
const [isReady, cancel] = hook.result.current;
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
act(() => {
|
||||
cancel();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(isReady()).toBe(null);
|
||||
});
|
||||
|
||||
it('third function should reset timeout', done => {
|
||||
const [spy, hook] = getHook();
|
||||
const [isReady, cancel, reset] = hook.result.current;
|
||||
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
act(() => {
|
||||
cancel();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
|
||||
expect(isReady()).toBe(null);
|
||||
|
||||
act(() => {
|
||||
reset();
|
||||
});
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(isReady()).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
});
|
||||
|
||||
it('should reset timeout on delay change', done => {
|
||||
const [spy, hook] = getHook(15);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
hook.rerender({ delay: 5 });
|
||||
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(spy).toHaveBeenCalledTimes(3);
|
||||
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(15);
|
||||
});
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(useTimeout).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return three functions', () => {
|
||||
const hook = renderHook(() => useTimeout(5));
|
||||
|
||||
expect(hook.result.current.length).toBe(3);
|
||||
expect(typeof hook.result.current[0]).toBe('function');
|
||||
expect(typeof hook.result.current[1]).toBe('function');
|
||||
expect(typeof hook.result.current[2]).toBe('function');
|
||||
});
|
||||
|
||||
function getHook(ms: number = 5): [jest.Mock, RenderHookResult<{ delay: number }, UseTimeoutReturn>] {
|
||||
const spy = jest.fn();
|
||||
return [
|
||||
spy,
|
||||
renderHook(
|
||||
({ delay = 5 }) => {
|
||||
spy();
|
||||
return useTimeout(delay);
|
||||
},
|
||||
{ initialProps: { delay: ms } }
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
it('should re-render component after given amount of time', done => {
|
||||
const [spy, hook] = getHook();
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
});
|
||||
|
||||
it('should cancel timeout on unmount', () => {
|
||||
const [spy, hook] = getHook();
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
hook.unmount();
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('first function should return actual state of timeout', done => {
|
||||
let [, hook] = getHook();
|
||||
let [isReady] = hook.result.current;
|
||||
|
||||
expect(isReady()).toBe(false);
|
||||
hook.unmount();
|
||||
expect(isReady()).toBe(null);
|
||||
|
||||
[, hook] = getHook();
|
||||
[isReady] = hook.result.current;
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(isReady()).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
});
|
||||
|
||||
it('second function should cancel timeout', () => {
|
||||
const [spy, hook] = getHook();
|
||||
const [isReady, cancel] = hook.result.current;
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
act(() => {
|
||||
cancel();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(isReady()).toBe(null);
|
||||
});
|
||||
|
||||
it('third function should reset timeout', done => {
|
||||
const [spy, hook] = getHook();
|
||||
const [isReady, cancel, reset] = hook.result.current;
|
||||
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
act(() => {
|
||||
cancel();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
|
||||
expect(isReady()).toBe(null);
|
||||
|
||||
act(() => {
|
||||
reset();
|
||||
});
|
||||
expect(isReady()).toBe(false);
|
||||
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(isReady()).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(5);
|
||||
});
|
||||
|
||||
it('should reset timeout on delay change', done => {
|
||||
const [spy, hook] = getHook(15);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
hook.rerender({ delay: 5 });
|
||||
|
||||
hook.waitForNextUpdate().then(() => {
|
||||
expect(spy).toHaveBeenCalledTimes(3);
|
||||
|
||||
done();
|
||||
});
|
||||
jest.advanceTimersByTime(15);
|
||||
});
|
||||
|
||||
56
src/__tests__/useTween.test.ts
Normal file
56
src/__tests__/useTween.test.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import useTween from '../useTween';
|
||||
import * as useRaf from '../useRaf';
|
||||
import { easing } from 'ts-easing';
|
||||
|
||||
let spyUseRaf;
|
||||
let spyEasingInCirc;
|
||||
let spyEasingOutCirc;
|
||||
|
||||
beforeEach(() => {
|
||||
spyUseRaf = jest.spyOn(useRaf, 'default').mockReturnValue(17);
|
||||
spyEasingInCirc = jest.spyOn(easing, 'inCirc').mockReturnValue(999999);
|
||||
spyEasingOutCirc = jest.spyOn(easing, 'outCirc').mockReturnValue(101010);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should init corresponding utils with default values', () => {
|
||||
const { result } = renderHook(() => useTween());
|
||||
|
||||
expect(result.current).toBe(999999);
|
||||
expect(spyEasingInCirc).toHaveBeenCalledTimes(1);
|
||||
expect(spyEasingInCirc).toHaveBeenCalledWith(17);
|
||||
expect(spyUseRaf).toHaveBeenCalledTimes(1);
|
||||
expect(spyUseRaf).toHaveBeenCalledWith(200, 0);
|
||||
});
|
||||
|
||||
it('should init corresponding utils with custom values', () => {
|
||||
const { result } = renderHook(() => useTween('outCirc', 500, 15));
|
||||
|
||||
expect(result.current).toBe(101010);
|
||||
expect(spyEasingOutCirc).toHaveBeenCalledTimes(1);
|
||||
expect(spyEasingOutCirc).toHaveBeenCalledWith(17);
|
||||
expect(spyUseRaf).toHaveBeenCalledTimes(1);
|
||||
expect(spyUseRaf).toHaveBeenCalledWith(500, 15);
|
||||
});
|
||||
|
||||
describe('when invalid easing name is provided', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
jest.spyOn(console, 'trace').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it('should log an error', () => {
|
||||
const { result } = renderHook(() => useTween('grijanderl'));
|
||||
|
||||
expect(result.current).toBe(0);
|
||||
expect(console.error).toHaveBeenCalledTimes(1);
|
||||
expect(console.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('useTween() expected "easingName" property to be a valid easing function name')
|
||||
);
|
||||
expect(console.trace).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
26
src/__tests__/useUpdate.test.ts
Normal file
26
src/__tests__/useUpdate.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import useUpdate from '../useUpdate';
|
||||
|
||||
it('should init update function', () => {
|
||||
const { result } = renderHook(() => useUpdate());
|
||||
const update = result.current;
|
||||
|
||||
expect(update).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
it('should forces a re-render every time update function is called', () => {
|
||||
let renderCount = 0;
|
||||
const { result } = renderHook(() => {
|
||||
renderCount++;
|
||||
return useUpdate();
|
||||
});
|
||||
const update = result.current;
|
||||
|
||||
expect(renderCount).toBe(1);
|
||||
|
||||
act(() => update());
|
||||
expect(renderCount).toBe(2);
|
||||
|
||||
act(() => update());
|
||||
expect(renderCount).toBe(3);
|
||||
});
|
||||
@ -1,31 +1,37 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
import { Spring, SpringSystem } from 'rebound';
|
||||
|
||||
const useSpring = (targetValue: number = 0, tension: number = 50, friction: number = 3) => {
|
||||
const [spring, setSpring] = useState<Spring | null>(null);
|
||||
const [value, setValue] = useState<number>(targetValue);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = {
|
||||
// memoize listener to being able to unsubscribe later properly, otherwise
|
||||
// listener fn will be different on each re-render and wouldn't unsubscribe properly.
|
||||
const listener = useMemo(
|
||||
() => ({
|
||||
onSpringUpdate: currentSpring => {
|
||||
const newValue = currentSpring.getCurrentValue();
|
||||
setValue(newValue);
|
||||
},
|
||||
};
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!spring) {
|
||||
const newSpring = new SpringSystem().createSpring(tension, friction);
|
||||
newSpring.setCurrentValue(targetValue);
|
||||
setSpring(newSpring);
|
||||
newSpring.addListener(listener);
|
||||
return;
|
||||
}
|
||||
|
||||
return () => {
|
||||
spring.removeListener(listener);
|
||||
setSpring(null);
|
||||
if (spring) {
|
||||
spring.removeListener(listener);
|
||||
setSpring(null);
|
||||
}
|
||||
};
|
||||
}, [tension, friction]);
|
||||
}, [tension, friction, spring]);
|
||||
|
||||
useEffect(() => {
|
||||
if (spring) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user