diff --git a/__tests__/useStopwatch.test.ts b/__tests__/useStopwatch.test.ts index e1ff9b6..febca30 100644 --- a/__tests__/useStopwatch.test.ts +++ b/__tests__/useStopwatch.test.ts @@ -83,6 +83,19 @@ describe('useStopwatch', () => { expect(result.current.seconds).toBe(0); expect(result.current.milliseconds).toBe(0); }); + + test('non Date offsetTimestamp values', () => { + const { result } = renderHook(() => + useStopwatch({ offsetTimestamp: {} as unknown as Date }) + ); + + expect(result.current.seconds).toBe(0); + expect(result.current.totalMilliseconds).toBe(0); + + act(() => { jest.advanceTimersByTime(3000); }); + + expect(result.current.seconds).toBe(3); + }); }); describe('interval', () => { @@ -404,5 +417,40 @@ describe('useStopwatch', () => { expect(result.current.isRunning).toBe(true); expect(result.current.seconds).toBe(5); }); + + test('calling reset with a non Date argument resets the stopwatch to 0', () => { + const { result } = renderHook(() => + useStopwatch() + ); + + act(() => { jest.advanceTimersByTime(5000); }); + + expect(result.current.seconds).toBe(5); + + // react passes the event object when reset is used directly as onClick={reset} + act(() => result.current.reset({ type: 'click' } as unknown as Date)); + + expect(result.current.seconds).toBe(0); + + act(() => { jest.advanceTimersByTime(2000); }); + + expect(result.current.isRunning).toBe(true); + expect(result.current.seconds).toBe(2); + }); + + test('calling reset with an invalid Date resets the stopwatch to 0', () => { + const { result } = renderHook(() => + useStopwatch() + ); + + act(() => { jest.advanceTimersByTime(5000); }); + + expect(result.current.seconds).toBe(5); + + act(() => result.current.reset(new Date('invalid'))); + + expect(result.current.seconds).toBe(0); + expect(result.current.totalMilliseconds).toBe(0); + }); }); }); \ No newline at end of file diff --git a/demo/components/UseStopwatchDemo.tsx b/demo/components/UseStopwatchDemo.tsx index 0b05489..bf033b2 100644 --- a/demo/components/UseStopwatchDemo.tsx +++ b/demo/components/UseStopwatchDemo.tsx @@ -3,7 +3,7 @@ import { useStopwatch } from '../../src/index'; import Button from './Button'; import TimerStyled from './TimerStyled'; -export default function UseStopwatchDemo({ interval }: { interval: number}) { +export default function UseStopwatchDemo({ interval }: { interval: number }) { const { milliseconds, seconds, @@ -22,7 +22,7 @@ export default function UseStopwatchDemo({ interval }: { interval: number}) { - + ); } diff --git a/readme.md b/readme.md index 220bc7c..8e285f7 100644 --- a/readme.md +++ b/readme.md @@ -144,7 +144,7 @@ function MyStopwatch() {

{isRunning ? 'Running' : 'Not running'}

- + ); } diff --git a/src/useStopwatch.ts b/src/useStopwatch.ts index 13e3ecf..52c5053 100644 --- a/src/useStopwatch.ts +++ b/src/useStopwatch.ts @@ -1,4 +1,4 @@ -import { useState, useCallback } from 'react'; +import { useState, useCallback, SyntheticEvent } from 'react'; import { Time } from './utils'; import { useInterval } from './hooks'; import { SECOND_INTERVAL } from './constants'; @@ -11,14 +11,14 @@ export type useStopwatchSettingsType = { }; export type useStopwatchResultType = TimeFromMillisecondsType & { - start: () => void, - pause: () => void, - reset: (offset?: Date, newAutoStart?: boolean) => void, + start: () => void, + pause: () => void, + reset: (offset?: Date | SyntheticEvent, newAutoStart?: boolean) => void, isRunning: boolean, }; export default function useStopwatch({ autoStart = true, offsetTimestamp, interval: customInterval = SECOND_INTERVAL }: useStopwatchSettingsType = {}): useStopwatchResultType { - const offsetMilliseconds = offsetTimestamp ? Time.getMillisecondsFromExpiry(offsetTimestamp) : 0; + const offsetMilliseconds = Time.isValidDate(offsetTimestamp) ? Time.getMillisecondsFromExpiry(offsetTimestamp) : 0; const [prevTime, setPrevTime] = useState(new Date().getTime() - new Date(offsetMilliseconds).getTime()); const [milliseconds, setMilliseconds] = useState(Time.getMillisecondsFromPrevTime(prevTime || 0)); const [isRunning, setIsRunning] = useState(autoStart); @@ -45,8 +45,8 @@ export default function useStopwatch({ autoStart = true, offsetTimestamp, interv } }, [prevTime, isRunning]); - const reset = useCallback((offset?: Date, newAutoStart = true) => { - const newOffsetMilliseconds = offset ? Time.getMillisecondsFromExpiry(offset) : 0; + const reset = useCallback((offset?: Date | SyntheticEvent, newAutoStart = true) => { + const newOffsetMilliseconds = Time.isValidDate(offset) ? Time.getMillisecondsFromExpiry(offset) : 0; const newPrevTime = new Date().getTime() - new Date(newOffsetMilliseconds).getTime(); const newMilliseconds = Time.getMillisecondsFromPrevTime(newPrevTime); const millisecondsOffset = SECOND_INTERVAL - (newMilliseconds % SECOND_INTERVAL); diff --git a/src/utils/Time.ts b/src/utils/Time.ts index 21d9758..41365e2 100644 --- a/src/utils/Time.ts +++ b/src/utils/Time.ts @@ -40,6 +40,11 @@ export default class Time { }; } + static isValidDate(value: unknown): value is Date { + return Object.prototype.toString.call(value) === '[object Date]' + && !Number.isNaN((value as Date).getTime()); + } + static getMillisecondsFromExpiry(expiry: Date): number { const now = new Date().getTime(); const milliSecondsDistance = expiry?.getTime() - now;