import { act, renderHook } from '@testing-library/react'; import { useCountdown } from './useCountdown'; describe('useCountdown', () => { beforeEach(() => jest.useFakeTimers()); afterEach(() => jest.useRealTimers()); it('starts at zero and inactive', () => { const { result } = renderHook(() => useCountdown()); expect(result.current.seconds).toBe(0); expect(result.current.isActive).toBe(false); }); it('counts down one second at a time and stops at zero', () => { const { result } = renderHook(() => useCountdown()); act(() => result.current.start(3)); expect(result.current.seconds).toBe(3); expect(result.current.isActive).toBe(true); act(() => jest.advanceTimersByTime(1000)); expect(result.current.seconds).toBe(2); act(() => jest.advanceTimersByTime(2000)); expect(result.current.seconds).toBe(0); expect(result.current.isActive).toBe(false); // No leaked interval keeps decrementing past zero. act(() => jest.advanceTimersByTime(3000)); expect(result.current.seconds).toBe(0); }); it('restarting resets the remaining seconds', () => { const { result } = renderHook(() => useCountdown()); act(() => result.current.start(2)); act(() => jest.advanceTimersByTime(1000)); expect(result.current.seconds).toBe(1); act(() => result.current.start(5)); expect(result.current.seconds).toBe(5); }); it('stop() halts the countdown', () => { const { result } = renderHook(() => useCountdown()); act(() => result.current.start(5)); act(() => result.current.stop()); act(() => jest.advanceTimersByTime(3000)); expect(result.current.seconds).toBe(5); }); });