diff --git a/packages/core/src/bundle/hooks/useInterval/useInterval.js b/packages/core/src/bundle/hooks/useInterval/useInterval.js index 82789190..19a2d555 100644 --- a/packages/core/src/bundle/hooks/useInterval/useInterval.js +++ b/packages/core/src/bundle/hooks/useInterval/useInterval.js @@ -24,10 +24,10 @@ import { useEffect, useRef, useState } from 'react'; */ export const useInterval = (...params) => { const callback = params[0]; - const interval = (typeof params[1] === 'number' ? params[1] : params[1].interval) ?? 1000; + const interval = (typeof params[1] === 'number' ? params[1] : params[1]?.interval) ?? 1000; const options = typeof params[1] === 'object' ? params[1] : params[2]; const immediately = options?.immediately ?? true; - const [active, setActive] = useState(immediately ?? true); + const [active, setActive] = useState(immediately); const intervalIdRef = useRef(undefined); const internalCallbackRef = useRef(callback); internalCallbackRef.current = callback; @@ -39,11 +39,8 @@ export const useInterval = (...params) => { }; }, [active, interval]); const pause = () => setActive(false); - const resume = () => { - if (interval <= 0) return; - setActive(true); - }; - const toggle = (value = !active) => setActive(value); + const resume = () => setActive(true); + const toggle = () => setActive((prev) => !prev); return { active, pause, diff --git a/packages/core/src/bundle/hooks/useStopwatch/useStopwatch.js b/packages/core/src/bundle/hooks/useStopwatch/useStopwatch.js index ca5c4733..6da049d9 100644 --- a/packages/core/src/bundle/hooks/useStopwatch/useStopwatch.js +++ b/packages/core/src/bundle/hooks/useStopwatch/useStopwatch.js @@ -22,74 +22,44 @@ const getStopwatchTime = (time) => { * * @overload * @param {number} [initialTime=0] The initial time of the timer - * @param {boolean} [options.enabled=true] The enabled state of the timer + * @param {boolean} [options.immediately=false] Start the stopwatch immediately * @returns {UseStopwatchReturn} An object containing the current time and functions to interact with the timer * * @example - * const { seconds, minutes, start, pause, reset } = useStopwatch(1000, { enabled: false }); + * const { seconds, minutes, start, pause, reset } = useStopwatch(1000, { immediately: false }); * * @overload - * @param {number} [options.initialTime=0] -The initial time of the timer - * @param {boolean} [options.enabled=true] The enabled state of the timer + * @param {number} [options.initialTime=0] The initial time of the timer + * @param {boolean} [options.immediately=false] Start the stopwatch immediately * @returns {UseStopwatchReturn} An object containing the current time and functions to interact with the timer * * @example - * const { seconds, minutes, start, pause, reset } = useStopwatch({ initialTime: 1000, enabled: false }); + * const { seconds, minutes, start, pause, reset } = useStopwatch({ initialTime: 1000, immediately: false }); */ export const useStopwatch = (...params) => { const initialTime = (typeof params[0] === 'number' ? params[0] : params[0]?.initialTime) ?? 0; const options = typeof params[0] === 'number' ? params[1] : params[0]; const immediately = options?.immediately ?? false; - const [time, setTime] = useState(getStopwatchTime(initialTime)); - const [paused, setPaused] = useState(!immediately && !initialTime); + const [count, setCount] = useState(initialTime); + const [paused, setPaused] = useState(!immediately); + useEffect(() => { + setCount(initialTime); + }, [initialTime]); useEffect(() => { if (paused) return; const onInterval = () => { - setTime((prevTime) => { - const updatedCount = prevTime.count + 1; - if (updatedCount % 60 === 0) { - return { - ...prevTime, - minutes: prevTime.minutes + 1, - seconds: 0, - count: updatedCount - }; - } - if (updatedCount % (60 * 60) === 0) { - return { - ...prevTime, - hours: prevTime.hours + 1, - minutes: 0, - seconds: 0, - count: updatedCount - }; - } - if (updatedCount % (60 * 60 * 24) === 0) { - return { - ...prevTime, - days: prevTime.days + 1, - hours: 0, - minutes: 0, - seconds: 0, - count: updatedCount - }; - } - return { - ...prevTime, - seconds: prevTime.seconds + 1, - count: updatedCount - }; - }); + setCount((prevCount) => prevCount + 1); }; - const interval = setInterval(() => onInterval(), 1000); + const interval = setInterval(onInterval, 1000); return () => clearInterval(interval); }, [paused]); + const time = getStopwatchTime(count); return { ...time, paused, pause: () => setPaused(true), start: () => setPaused(false), - reset: () => setTime(getStopwatchTime(initialTime)), + reset: () => setCount(initialTime), toggle: () => setPaused((prevPause) => !prevPause) }; }; diff --git a/packages/core/src/hooks/useInterval/useInterval.ts b/packages/core/src/hooks/useInterval/useInterval.ts index 45aa5eab..1ad124c4 100644 --- a/packages/core/src/hooks/useInterval/useInterval.ts +++ b/packages/core/src/hooks/useInterval/useInterval.ts @@ -52,14 +52,15 @@ export const useInterval = ((...params: any[]): UseIntervalReturn => { const interval = ((typeof params[1] === 'number' ? params[1] - : (params[1] as UseIntervalOptions & { interval?: number }).interval) as number) ?? 1000; + : (params[1] as (UseIntervalOptions & { interval?: number }) | undefined) + ?.interval) as number) ?? 1000; const options = typeof params[1] === 'object' ? (params[1] as (UseIntervalOptions & { interval?: number }) | undefined) : (params[2] as UseIntervalOptions | undefined); const immediately = options?.immediately ?? true; - const [active, setActive] = useState(immediately ?? true); + const [active, setActive] = useState(immediately); const intervalIdRef = useRef>(undefined); const internalCallbackRef = useRef(callback); @@ -76,12 +77,9 @@ export const useInterval = ((...params: any[]): UseIntervalReturn => { const pause = () => setActive(false); - const resume = () => { - if (interval <= 0) return; - setActive(true); - }; + const resume = () => setActive(true); - const toggle = (value = !active) => setActive(value); + const toggle = () => setActive((prev) => !prev); return { active, diff --git a/packages/core/src/hooks/useStopwatch/useStopwatch.test.ts b/packages/core/src/hooks/useStopwatch/useStopwatch.test.ts index 2919e423..0c67aafa 100644 --- a/packages/core/src/hooks/useStopwatch/useStopwatch.test.ts +++ b/packages/core/src/hooks/useStopwatch/useStopwatch.test.ts @@ -167,3 +167,57 @@ it('Should cleanup interval on unmount', () => { expect(clearIntervalSpy).toHaveBeenCalled(); clearIntervalSpy.mockRestore(); }); + +it('Should correctly transition from 59 seconds to 1 minute', () => { + const { result } = renderHook(useStopwatch); + + act(() => result.current.start()); + + act(() => vi.advanceTimersByTime(59_000)); + + expect(result.current.seconds).toBe(59); + expect(result.current.minutes).toBe(0); + + act(() => vi.advanceTimersByTime(1_000)); + + expect(result.current.seconds).toBe(0); + expect(result.current.minutes).toBe(1); +}); + +it('Should correctly transition from 59 minutes 59 seconds to 1 hour', () => { + const { result } = renderHook(useStopwatch); + + act(() => result.current.start()); + + act(() => vi.advanceTimersByTime(3_599_000)); + + expect(result.current.hours).toBe(0); + expect(result.current.minutes).toBe(59); + expect(result.current.seconds).toBe(59); + + act(() => vi.advanceTimersByTime(1_000)); + + expect(result.current.hours).toBe(1); + expect(result.current.minutes).toBe(0); + expect(result.current.seconds).toBe(0); +}); + +it('Should correctly transition from 23:59:59 to 1 day', () => { + const { result } = renderHook(useStopwatch); + + act(() => result.current.start()); + + act(() => vi.advanceTimersByTime(86_399_000)); + + expect(result.current.days).toBe(0); + expect(result.current.hours).toBe(23); + expect(result.current.minutes).toBe(59); + expect(result.current.seconds).toBe(59); + + act(() => vi.advanceTimersByTime(1_000)); + + expect(result.current.days).toBe(1); + expect(result.current.hours).toBe(0); + expect(result.current.minutes).toBe(0); + expect(result.current.seconds).toBe(0); +}); diff --git a/packages/core/src/hooks/useStopwatch/useStopwatch.ts b/packages/core/src/hooks/useStopwatch/useStopwatch.ts index 6c4ccc3a..fc248911 100644 --- a/packages/core/src/hooks/useStopwatch/useStopwatch.ts +++ b/packages/core/src/hooks/useStopwatch/useStopwatch.ts @@ -28,8 +28,6 @@ export interface UseStopwatchReturn { hours: number; /** The minute count of the stopwatch */ minutes: number; - /** The over state of the stopwatch */ - over: boolean; /** The paused state of the stopwatch */ paused: boolean; /** The second count of the stopwatch */ @@ -62,19 +60,19 @@ interface UseStopwatch { * * @overload * @param {number} [initialTime=0] The initial time of the timer - * @param {boolean} [options.enabled=true] The enabled state of the timer + * @param {boolean} [options.immediately=false] Start the stopwatch immediately * @returns {UseStopwatchReturn} An object containing the current time and functions to interact with the timer * * @example - * const { seconds, minutes, start, pause, reset } = useStopwatch(1000, { enabled: false }); + * const { seconds, minutes, start, pause, reset } = useStopwatch(1000, { immediately: false }); * * @overload - * @param {number} [options.initialTime=0] -The initial time of the timer - * @param {boolean} [options.enabled=true] The enabled state of the timer + * @param {number} [options.initialTime=0] The initial time of the timer + * @param {boolean} [options.immediately=false] Start the stopwatch immediately * @returns {UseStopwatchReturn} An object containing the current time and functions to interact with the timer * * @example - * const { seconds, minutes, start, pause, reset } = useStopwatch({ initialTime: 1000, enabled: false }); + * const { seconds, minutes, start, pause, reset } = useStopwatch({ initialTime: 1000, immediately: false }); */ export const useStopwatch = ((...params: any[]) => { const initialTime = @@ -89,63 +87,31 @@ export const useStopwatch = ((...params: any[]) => { const immediately = options?.immediately ?? false; - const [time, setTime] = useState(getStopwatchTime(initialTime)); - const [paused, setPaused] = useState(!immediately && !initialTime); + const [count, setCount] = useState(initialTime); + const [paused, setPaused] = useState(!immediately); + + useEffect(() => { + setCount(initialTime); + }, [initialTime]); useEffect(() => { if (paused) return; const onInterval = () => { - setTime((prevTime) => { - const updatedCount = prevTime.count + 1; - - if (updatedCount % 60 === 0) { - return { - ...prevTime, - minutes: prevTime.minutes + 1, - seconds: 0, - count: updatedCount - }; - } - - if (updatedCount % (60 * 60) === 0) { - return { - ...prevTime, - hours: prevTime.hours + 1, - minutes: 0, - seconds: 0, - count: updatedCount - }; - } - - if (updatedCount % (60 * 60 * 24) === 0) { - return { - ...prevTime, - days: prevTime.days + 1, - hours: 0, - minutes: 0, - seconds: 0, - count: updatedCount - }; - } - - return { - ...prevTime, - seconds: prevTime.seconds + 1, - count: updatedCount - }; - }); + setCount((prevCount) => prevCount + 1); }; - const interval = setInterval(() => onInterval(), 1000); + const interval = setInterval(onInterval, 1000); return () => clearInterval(interval); }, [paused]); + const time = getStopwatchTime(count); + return { ...time, paused, pause: () => setPaused(true), start: () => setPaused(false), - reset: () => setTime(getStopwatchTime(initialTime)), + reset: () => setCount(initialTime), toggle: () => setPaused((prevPause) => !prevPause) }; }) as UseStopwatch;