import { jsx as _jsx } from "react/jsx-runtime";
// @vitest-environment happy-dom
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { render } from '@testing-library/react';
import { useIdleLogout } from '../../hooks/useIdleLogout.js';
function Harness({ enabled, timeoutMs, onIdle, warningMs, onWarn, onWarnCancelled, stayActiveRef, }) {
    const { stayActive } = useIdleLogout({
        enabled,
        timeoutMs,
        onIdle,
        warningMs,
        onWarn,
        onWarnCancelled,
    });
    if (stayActiveRef)
        stayActiveRef.current = stayActive;
    return null;
}
const TIMEOUT_MS = 60_000;
beforeEach(() => {
    vi.useFakeTimers();
});
afterEach(() => {
    vi.useRealTimers();
});
describe('useIdleLogout', () => {
    it('fires onIdle exactly once after the timeout with no activity', () => {
        const onIdle = vi.fn();
        render(_jsx(Harness, { enabled: true, timeoutMs: TIMEOUT_MS, onIdle: onIdle }));
        vi.advanceTimersByTime(TIMEOUT_MS - 1_000);
        expect(onIdle).not.toHaveBeenCalled();
        vi.advanceTimersByTime(TIMEOUT_MS);
        expect(onIdle).toHaveBeenCalledTimes(1);
        // The idle callback must not re-fire on subsequent ticks.
        vi.advanceTimersByTime(TIMEOUT_MS * 3);
        expect(onIdle).toHaveBeenCalledTimes(1);
    });
    it('resets the idle deadline on user activity', () => {
        const onIdle = vi.fn();
        render(_jsx(Harness, { enabled: true, timeoutMs: TIMEOUT_MS, onIdle: onIdle }));
        // Just before the deadline the user types — the clock restarts.
        vi.advanceTimersByTime(TIMEOUT_MS - 5_000);
        window.dispatchEvent(new Event('keydown'));
        vi.advanceTimersByTime(TIMEOUT_MS - 5_000);
        expect(onIdle).not.toHaveBeenCalled();
        // A full quiet timeout after the last activity signs the user out.
        vi.advanceTimersByTime(TIMEOUT_MS);
        expect(onIdle).toHaveBeenCalledTimes(1);
    });
    it('does nothing when disabled', () => {
        const onIdle = vi.fn();
        render(_jsx(Harness, { enabled: false, timeoutMs: TIMEOUT_MS, onIdle: onIdle }));
        vi.advanceTimersByTime(TIMEOUT_MS * 10);
        expect(onIdle).not.toHaveBeenCalled();
    });
    it('tears everything down on unmount', () => {
        const onIdle = vi.fn();
        const { unmount } = render(_jsx(Harness, { enabled: true, timeoutMs: TIMEOUT_MS, onIdle: onIdle }));
        unmount();
        vi.advanceTimersByTime(TIMEOUT_MS * 10);
        expect(onIdle).not.toHaveBeenCalled();
    });
    it('checks the deadline immediately when a hidden tab becomes visible', () => {
        const onIdle = vi.fn();
        render(_jsx(Harness, { enabled: true, timeoutMs: TIMEOUT_MS, onIdle: onIdle }));
        // Simulate the deadline passing while the tab was hidden (the interval is
        // shared with visibility, so freeze it by only moving the mocked clock).
        vi.setSystemTime(Date.now() + TIMEOUT_MS + 1_000);
        document.dispatchEvent(new Event('visibilitychange'));
        expect(onIdle).toHaveBeenCalledTimes(1);
    });
    describe('warning window', () => {
        const WARNING_MS = 20_000;
        it('fires onWarn once inside the warning window, then onIdle at the deadline', () => {
            const onIdle = vi.fn();
            const onWarn = vi.fn();
            render(_jsx(Harness, { enabled: true, timeoutMs: TIMEOUT_MS, warningMs: WARNING_MS, onIdle: onIdle, onWarn: onWarn }));
            // Before the warning window: nothing.
            vi.advanceTimersByTime(TIMEOUT_MS - WARNING_MS - 5_000);
            expect(onWarn).not.toHaveBeenCalled();
            // Inside the window: warn exactly once, no logout yet.
            vi.advanceTimersByTime(10_000);
            expect(onWarn).toHaveBeenCalledTimes(1);
            expect(onIdle).not.toHaveBeenCalled();
            // Deadline: logout, and the warning doesn't re-fire.
            vi.advanceTimersByTime(TIMEOUT_MS);
            expect(onIdle).toHaveBeenCalledTimes(1);
            expect(onWarn).toHaveBeenCalledTimes(1);
        });
        it('activity after the warning cancels it and resets the clock', () => {
            const onIdle = vi.fn();
            const onWarn = vi.fn();
            const onWarnCancelled = vi.fn();
            render(_jsx(Harness, { enabled: true, timeoutMs: TIMEOUT_MS, warningMs: WARNING_MS, onIdle: onIdle, onWarn: onWarn, onWarnCancelled: onWarnCancelled }));
            vi.advanceTimersByTime(TIMEOUT_MS - 10_000);
            expect(onWarn).toHaveBeenCalledTimes(1);
            window.dispatchEvent(new Event('keydown'));
            expect(onWarnCancelled).toHaveBeenCalledTimes(1);
            // The clock restarted: warn again on the next quiet cycle, no logout yet.
            vi.advanceTimersByTime(TIMEOUT_MS - 10_000);
            expect(onWarn).toHaveBeenCalledTimes(2);
            expect(onIdle).not.toHaveBeenCalled();
        });
        it('stayActive resets the clock like real activity', () => {
            const onIdle = vi.fn();
            const onWarn = vi.fn();
            const onWarnCancelled = vi.fn();
            const stayActiveRef = { current: null };
            render(_jsx(Harness, { enabled: true, timeoutMs: TIMEOUT_MS, warningMs: WARNING_MS, onIdle: onIdle, onWarn: onWarn, onWarnCancelled: onWarnCancelled, stayActiveRef: stayActiveRef }));
            vi.advanceTimersByTime(TIMEOUT_MS - 10_000);
            expect(onWarn).toHaveBeenCalledTimes(1);
            stayActiveRef.current?.();
            expect(onWarnCancelled).toHaveBeenCalledTimes(1);
            vi.advanceTimersByTime(TIMEOUT_MS - 15_000);
            expect(onIdle).not.toHaveBeenCalled();
        });
    });
});
//# sourceMappingURL=useIdleLogout.test.js.map