-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: correct useSpring() hook behaviour
Add tests for "Animation" block.
- Loading branch information
Showing
9 changed files
with
477 additions
and
127 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); |
Oops, something went wrong.