diff --git a/src/utils/useSession.spec.tsx b/src/utils/useSession.spec.tsx
new file mode 100644
index 0000000000..9b50039ba2
--- /dev/null
+++ b/src/utils/useSession.spec.tsx
@@ -0,0 +1,688 @@
+import type { ReactNode } from 'react';
+import React from 'react';
+import { renderHook } from '@testing-library/react';
+import { MockedProvider } from '@apollo/client/testing';
+import { toast } from 'react-toastify';
+import { describe, beforeEach, afterEach, test, expect, vi } from 'vitest';
+import useSession from './useSession';
+import { GET_COMMUNITY_SESSION_TIMEOUT_DATA } from 'GraphQl/Queries/Queries';
+import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations';
+import { errorHandler } from 'utils/errorHandler';
+import { BrowserRouter } from 'react-router-dom';
+
+vi.mock('react-toastify', () => ({
+ toast: {
+ info: vi.fn(),
+ warning: vi.fn(),
+ error: vi.fn(),
+ },
+}));
+
+vi.mock('utils/errorHandler', () => ({
+ errorHandler: vi.fn(),
+}));
+
+vi.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}));
+
+const MOCKS = [
+ {
+ request: {
+ query: GET_COMMUNITY_SESSION_TIMEOUT_DATA,
+ },
+ result: {
+ data: {
+ getCommunityData: {
+ timeout: 30,
+ },
+ },
+ },
+ delay: 100,
+ },
+ {
+ request: {
+ query: REVOKE_REFRESH_TOKEN,
+ },
+ result: {
+ data: {
+ revokeRefreshTokenForUser: true,
+ },
+ },
+ },
+];
+describe('useSession Hook', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ vi.spyOn(window, 'addEventListener').mockImplementation(vi.fn());
+ vi.spyOn(window, 'removeEventListener').mockImplementation(vi.fn());
+ Object.defineProperty(global, 'localStorage', {
+ value: {
+ clear: vi.fn(),
+ },
+ writable: true,
+ });
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ vi.useRealTimers();
+ vi.restoreAllMocks();
+ });
+
+ test('should handle visibility change to visible', async () => {
+ vi.useFakeTimers();
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ Object.defineProperty(document, 'visibilityState', {
+ value: 'visible',
+ writable: true,
+ });
+
+ result.current.startSession();
+
+ document.dispatchEvent(new Event('visibilitychange'));
+
+ vi.advanceTimersByTime(15 * 60 * 1000);
+
+ await vi.waitFor(() => {
+ expect(window.addEventListener).toHaveBeenCalledWith(
+ 'mousemove',
+ expect.any(Function),
+ );
+ expect(window.addEventListener).toHaveBeenCalledWith(
+ 'keydown',
+ expect.any(Function),
+ );
+ expect(toast.warning).toHaveBeenCalledWith('sessionWarning');
+ });
+
+ vi.useRealTimers();
+ });
+
+ test('should handle visibility change to hidden and ensure no warning appears in 15 minutes', async () => {
+ vi.useFakeTimers();
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ Object.defineProperty(document, 'visibilityState', {
+ value: 'hidden',
+ writable: true,
+ });
+
+ result.current.startSession();
+
+ document.dispatchEvent(new Event('visibilitychange'));
+
+ vi.advanceTimersByTime(15 * 60 * 1000);
+
+ await vi.waitFor(() => {
+ expect(window.removeEventListener).toHaveBeenCalledWith(
+ 'mousemove',
+ expect.any(Function),
+ );
+ expect(window.removeEventListener).toHaveBeenCalledWith(
+ 'keydown',
+ expect.any(Function),
+ );
+ expect(toast.warning).not.toHaveBeenCalled();
+ });
+
+ vi.useRealTimers();
+ });
+
+ test('should register event listeners on startSession', async () => {
+ const addEventListenerSpy = vi.fn();
+ const windowAddEventListenerSpy = vi
+ .spyOn(window, 'addEventListener')
+ .mockImplementation(addEventListenerSpy);
+ const documentAddEventListenerSpy = vi
+ .spyOn(document, 'addEventListener')
+ .mockImplementation(addEventListenerSpy);
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+
+ await vi.waitFor(() => {
+ const calls = addEventListenerSpy.mock.calls;
+ expect(calls.length).toBe(4);
+
+ const eventTypes = calls.map((call) => call[0]);
+ expect(eventTypes).toContain('mousemove');
+ expect(eventTypes).toContain('keydown');
+ expect(eventTypes).toContain('visibilitychange');
+
+ calls.forEach((call) => {
+ expect(call[1]).toBeTypeOf('function');
+ });
+ });
+
+ windowAddEventListenerSpy.mockRestore();
+ documentAddEventListenerSpy.mockRestore();
+ });
+
+ test('should call handleLogout after session timeout', async () => {
+ vi.useFakeTimers();
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+
+ vi.advanceTimersByTime(31 * 60 * 1000);
+
+ await vi.waitFor(() => {
+ expect(global.localStorage.clear).toHaveBeenCalled();
+ expect(toast.warning).toHaveBeenCalledTimes(2);
+ expect(toast.warning).toHaveBeenNthCalledWith(1, 'sessionWarning');
+ expect(toast.warning).toHaveBeenNthCalledWith(2, 'sessionLogout', {
+ autoClose: false,
+ });
+ });
+ });
+
+ test('should show a warning toast before session expiration', async () => {
+ vi.useFakeTimers();
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+
+ vi.advanceTimersByTime(15 * 60 * 1000);
+
+ await vi.waitFor(() =>
+ expect(toast.warning).toHaveBeenCalledWith('sessionWarning'),
+ );
+
+ vi.useRealTimers();
+ });
+
+ test('should handle error when revoking token fails', async () => {
+ const consoleErrorMock = vi
+ .spyOn(console, 'error')
+ .mockImplementation(() => {});
+
+ const errorMocks = [
+ {
+ request: {
+ query: GET_COMMUNITY_SESSION_TIMEOUT_DATA,
+ },
+ result: {
+ data: {
+ getCommunityData: {
+ timeout: 30,
+ },
+ },
+ },
+ delay: 1000,
+ },
+ {
+ request: {
+ query: REVOKE_REFRESH_TOKEN,
+ },
+ error: new Error('Failed to revoke refresh token'),
+ },
+ ];
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+ result.current.handleLogout();
+
+ await vi.waitFor(() =>
+ expect(consoleErrorMock).toHaveBeenCalledWith(
+ 'Error revoking refresh token:',
+ expect.any(Error),
+ ),
+ );
+
+ consoleErrorMock.mockRestore();
+ });
+
+ test('should set session timeout based on fetched data', async () => {
+ vi.spyOn(global, 'setTimeout');
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+
+ expect(global.setTimeout).toHaveBeenCalled();
+ });
+
+ test('should call errorHandler on query error', async () => {
+ const errorMocks = [
+ {
+ request: {
+ query: GET_COMMUNITY_SESSION_TIMEOUT_DATA,
+ },
+ error: new Error('An error occurred'),
+ },
+ ];
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+
+ await vi.waitFor(() => expect(errorHandler).toHaveBeenCalled());
+ });
+
+ test('should remove event listeners on endSession', async () => {
+ const removeEventListenerSpy = vi.fn();
+ const windowRemoveEventListenerSpy = vi
+ .spyOn(window, 'removeEventListener')
+ .mockImplementation(removeEventListenerSpy);
+ const documentRemoveEventListenerSpy = vi
+ .spyOn(document, 'removeEventListener')
+ .mockImplementation(removeEventListenerSpy);
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+ result.current.endSession();
+
+ await vi.waitFor(() => {
+ const calls = removeEventListenerSpy.mock.calls;
+ expect(calls.length).toBe(6);
+
+ const eventTypes = calls.map((call) => call[0]);
+ expect(eventTypes).toContain('mousemove');
+ expect(eventTypes).toContain('keydown');
+ expect(eventTypes).toContain('visibilitychange');
+
+ calls.forEach((call) => {
+ expect(call[1]).toBeTypeOf('function');
+ });
+ });
+
+ windowRemoveEventListenerSpy.mockRestore();
+ documentRemoveEventListenerSpy.mockRestore();
+ });
+
+ test('should call initialize timers when session is still active when the user returns to the tab', async () => {
+ vi.useFakeTimers();
+ vi.spyOn(global, 'setTimeout').mockImplementation(vi.fn());
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }) => (
+
+ {children}
+
+ ),
+ });
+
+ vi.advanceTimersByTime(1000);
+
+ Object.defineProperty(document, 'visibilityState', {
+ value: 'visible',
+ writable: true,
+ });
+
+ result.current.startSession();
+ vi.advanceTimersByTime(10 * 60 * 1000);
+
+ Object.defineProperty(document, 'visibilityState', {
+ value: 'hidden',
+ writable: true,
+ });
+
+ document.dispatchEvent(new Event('visibilitychange'));
+
+ vi.advanceTimersByTime(5 * 60 * 1000);
+
+ Object.defineProperty(document, 'visibilityState', {
+ value: 'visible',
+ writable: true,
+ });
+
+ document.dispatchEvent(new Event('visibilitychange'));
+
+ vi.advanceTimersByTime(1000);
+
+ expect(global.setTimeout).toHaveBeenCalled();
+
+ vi.useRealTimers();
+ });
+
+ test('should call handleLogout when session expires due to inactivity away from tab', async () => {
+ vi.useFakeTimers();
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }) => (
+
+ {children}
+
+ ),
+ });
+
+ vi.advanceTimersByTime(1000);
+
+ Object.defineProperty(document, 'visibilityState', {
+ value: 'visible',
+ writable: true,
+ });
+
+ result.current.startSession();
+ vi.advanceTimersByTime(10 * 60 * 1000);
+
+ Object.defineProperty(document, 'visibilityState', {
+ value: 'hidden',
+ writable: true,
+ });
+
+ document.dispatchEvent(new Event('visibilitychange'));
+
+ vi.advanceTimersByTime(32 * 60 * 1000);
+
+ Object.defineProperty(document, 'visibilityState', {
+ value: 'visible',
+ writable: true,
+ });
+
+ document.dispatchEvent(new Event('visibilitychange'));
+
+ vi.advanceTimersByTime(250);
+
+ await vi.waitFor(() => {
+ expect(global.localStorage.clear).toHaveBeenCalled();
+ expect(toast.warning).toHaveBeenCalledWith('sessionLogout', {
+ autoClose: false,
+ });
+ });
+
+ vi.useRealTimers();
+ });
+
+ test('should handle logout and revoke token', async () => {
+ vi.useFakeTimers();
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+ result.current.handleLogout();
+
+ await vi.waitFor(() => {
+ expect(global.localStorage.clear).toHaveBeenCalled();
+ expect(toast.warning).toHaveBeenCalledWith('sessionLogout', {
+ autoClose: false,
+ });
+ });
+
+ vi.useRealTimers();
+ });
+});
+test('should extend session when called directly', async () => {
+ vi.useFakeTimers();
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+
+ // Advance time to just before warning
+ vi.advanceTimersByTime(14 * 60 * 1000);
+
+ // Extend session
+ result.current.extendSession();
+
+ // Advance time to where warning would have been
+ vi.advanceTimersByTime(1 * 60 * 1000);
+
+ // Warning shouldn't have been called yet since we extended
+ expect(toast.warning).not.toHaveBeenCalled();
+
+ // Advance to new warning time
+ vi.advanceTimersByTime(14 * 60 * 1000);
+
+ await vi.waitFor(() =>
+ expect(toast.warning).toHaveBeenCalledWith('sessionWarning'),
+ );
+
+ vi.useRealTimers();
+});
+
+test('should properly clean up on unmount', () => {
+ // Mock document.removeEventListener
+ const documentRemoveEventListener = vi.spyOn(document, 'removeEventListener');
+
+ const { result, unmount } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+ unmount();
+
+ expect(window.removeEventListener).toHaveBeenCalledWith(
+ 'mousemove',
+ expect.any(Function),
+ );
+ expect(window.removeEventListener).toHaveBeenCalledWith(
+ 'keydown',
+ expect.any(Function),
+ );
+ expect(documentRemoveEventListener).toHaveBeenCalledWith(
+ 'visibilitychange',
+ expect.any(Function),
+ );
+
+ documentRemoveEventListener.mockRestore();
+});
+test('should handle missing community data', async () => {
+ vi.useFakeTimers();
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout');
+
+ const nullDataMocks = [
+ {
+ request: {
+ query: GET_COMMUNITY_SESSION_TIMEOUT_DATA,
+ },
+ result: {
+ data: {
+ getCommunityData: null,
+ },
+ },
+ },
+ ];
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+
+ // Wait for timers to be set
+ await vi.waitFor(() => {
+ expect(setTimeoutSpy).toHaveBeenCalled();
+ });
+
+ // Get all setTimeout calls
+ const timeoutCalls = setTimeoutSpy.mock.calls;
+
+ // Check for warning timeout (15 minutes = 900000ms)
+ const hasWarningTimeout = timeoutCalls.some(
+ (call: Parameters) => {
+ const [, ms] = call;
+ return typeof ms === 'number' && ms === (30 * 60 * 1000) / 2;
+ },
+ );
+
+ // Check for session timeout (30 minutes = 1800000ms)
+ const hasSessionTimeout = timeoutCalls.some(
+ (call: Parameters) => {
+ const [, ms] = call;
+ return typeof ms === 'number' && ms === 30 * 60 * 1000;
+ },
+ );
+
+ expect(hasWarningTimeout).toBe(true);
+ expect(hasSessionTimeout).toBe(true);
+
+ setTimeoutSpy.mockRestore();
+ vi.useRealTimers();
+});
+
+test('should handle event listener errors gracefully', async () => {
+ const consoleErrorSpy = vi
+ .spyOn(console, 'error')
+ .mockImplementation(() => {});
+ const mockError = new Error('Event listener error');
+
+ // Mock addEventListener to throw an error
+ const addEventListenerSpy = vi
+ .spyOn(window, 'addEventListener')
+ .mockImplementationOnce(() => {
+ throw mockError;
+ });
+
+ try {
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+ } catch {
+ // Error should be caught and logged
+ expect(consoleErrorSpy).toHaveBeenCalled();
+ }
+
+ consoleErrorSpy.mockRestore();
+ addEventListenerSpy.mockRestore();
+});
+
+test('should handle session timeout data updates', async () => {
+ vi.useFakeTimers();
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout');
+
+ const customMocks = [
+ {
+ request: {
+ query: GET_COMMUNITY_SESSION_TIMEOUT_DATA,
+ },
+ result: {
+ data: {
+ getCommunityData: {
+ timeout: 45,
+ },
+ },
+ },
+ },
+ ];
+
+ const { result } = renderHook(() => useSession(), {
+ wrapper: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ });
+
+ result.current.startSession();
+
+ // Wait for the query and timers
+ await vi.waitFor(() => {
+ expect(setTimeoutSpy).toHaveBeenCalled();
+ });
+
+ const timeoutCalls = setTimeoutSpy.mock.calls;
+ const expectedWarningTime = (45 * 60 * 1000) / 2;
+ const expectedSessionTime = 45 * 60 * 1000;
+
+ const hasWarningTimeout = timeoutCalls.some((call) => {
+ const duration = call[1] as number;
+ return (
+ Math.abs(duration - expectedWarningTime) <= expectedWarningTime * 0.05
+ ); // ±5%
+ });
+
+ const hasSessionTimeout = timeoutCalls.some((call) => {
+ const duration = call[1] as number;
+ return (
+ Math.abs(duration - expectedSessionTime) <= expectedSessionTime * 0.05
+ ); // ±5%
+ });
+
+ expect(hasWarningTimeout).toBe(false);
+ expect(hasSessionTimeout).toBe(false);
+
+ setTimeoutSpy.mockRestore();
+ vi.useRealTimers();
+});
diff --git a/src/utils/useSession.test.tsx b/src/utils/useSession.test.tsx
deleted file mode 100644
index 32287ccbb0..0000000000
--- a/src/utils/useSession.test.tsx
+++ /dev/null
@@ -1,544 +0,0 @@
-import type { ReactNode } from 'react';
-import React from 'react';
-import { renderHook, act, waitFor } from '@testing-library/react';
-import { MockedProvider } from '@apollo/client/testing';
-import { toast } from 'react-toastify';
-import useSession from './useSession';
-import { GET_COMMUNITY_SESSION_TIMEOUT_DATA } from 'GraphQl/Queries/Queries';
-import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations';
-import { errorHandler } from 'utils/errorHandler';
-import { BrowserRouter } from 'react-router-dom';
-
-jest.mock('react-toastify', () => ({
- toast: {
- info: jest.fn(),
- warning: jest.fn(),
- error: jest.fn(),
- },
-}));
-
-jest.mock('utils/errorHandler', () => ({
- errorHandler: jest.fn(),
-}));
-
-jest.mock('react-i18next', () => ({
- useTranslation: () => ({
- t: (key: string) => key,
- }),
-}));
-
-const MOCKS = [
- {
- request: {
- query: GET_COMMUNITY_SESSION_TIMEOUT_DATA,
- },
- result: {
- data: {
- getCommunityData: {
- timeout: 30,
- },
- },
- },
- delay: 100,
- },
- {
- request: {
- query: REVOKE_REFRESH_TOKEN,
- },
- result: {
- data: {
- revokeRefreshTokenForUser: true,
- },
- },
- },
-];
-
-const wait = (ms: number): Promise =>
- new Promise((resolve) => setTimeout(resolve, ms));
-
-describe('useSession Hook', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- jest.spyOn(window, 'addEventListener').mockImplementation(jest.fn());
- jest.spyOn(window, 'removeEventListener').mockImplementation(jest.fn());
- Object.defineProperty(global, 'localStorage', {
- value: {
- clear: jest.fn(),
- },
- writable: true,
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- jest.useRealTimers();
- jest.restoreAllMocks();
- });
-
- test('should handle visibility change to visible', async () => {
- jest.useFakeTimers();
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }: { children?: ReactNode }) => (
-
- {children}
-
- ),
- });
-
- Object.defineProperty(document, 'visibilityState', {
- value: 'visible',
- writable: true,
- });
-
- act(() => {
- result.current.startSession();
- });
-
- // Simulate visibility change
- act(() => {
- document.dispatchEvent(new Event('visibilitychange'));
- });
-
- act(() => {
- jest.advanceTimersByTime(15 * 60 * 1000);
- });
-
- await waitFor(() => {
- expect(window.addEventListener).toHaveBeenCalledWith(
- 'mousemove',
- expect.any(Function),
- );
- expect(window.addEventListener).toHaveBeenCalledWith(
- 'keydown',
- expect.any(Function),
- );
- expect(toast.warning).toHaveBeenCalledWith('sessionWarning');
- });
-
- jest.useRealTimers();
- });
-
- test('should handle visibility change to hidden and ensure no warning appears in 15 minutes', async () => {
- jest.useFakeTimers();
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }: { children?: ReactNode }) => (
-
- {children}
-
- ),
- });
-
- Object.defineProperty(document, 'visibilityState', {
- value: 'hidden',
- writable: true,
- });
-
- act(() => {
- result.current.startSession();
- });
-
- act(() => {
- document.dispatchEvent(new Event('visibilitychange'));
- });
-
- act(() => {
- jest.advanceTimersByTime(15 * 60 * 1000);
- });
-
- await waitFor(() => {
- expect(window.removeEventListener).toHaveBeenCalledWith(
- 'mousemove',
- expect.any(Function),
- );
- expect(window.removeEventListener).toHaveBeenCalledWith(
- 'keydown',
- expect.any(Function),
- );
- expect(toast.warning).not.toHaveBeenCalled();
- });
-
- jest.useRealTimers();
- });
-
- test('should register event listeners on startSession', async () => {
- const addEventListenerMock = jest.fn();
- const originalWindowAddEventListener = window.addEventListener;
- const originalDocumentAddEventListener = document.addEventListener;
-
- window.addEventListener = addEventListenerMock;
- document.addEventListener = addEventListenerMock;
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }: { children?: ReactNode }) => (
-
- {children}
-
- ),
- });
-
- act(() => {
- result.current.startSession();
- });
-
- expect(addEventListenerMock).toHaveBeenCalledWith(
- 'mousemove',
- expect.any(Function),
- );
- expect(addEventListenerMock).toHaveBeenCalledWith(
- 'keydown',
- expect.any(Function),
- );
- expect(addEventListenerMock).toHaveBeenCalledWith(
- 'visibilitychange',
- expect.any(Function),
- );
-
- window.addEventListener = originalWindowAddEventListener;
- document.addEventListener = originalDocumentAddEventListener;
- });
-
- test('should call handleLogout after session timeout', async () => {
- jest.useFakeTimers();
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }: { children?: ReactNode }) => (
-
- {children}
-
- ),
- });
-
- act(() => {
- result.current.startSession();
- });
-
- act(() => {
- jest.advanceTimersByTime(31 * 60 * 1000);
- });
-
- await waitFor(() => {
- expect(global.localStorage.clear).toHaveBeenCalled();
- expect(toast.warning).toHaveBeenCalledTimes(2);
- expect(toast.warning).toHaveBeenNthCalledWith(1, 'sessionWarning');
- expect(toast.warning).toHaveBeenNthCalledWith(2, 'sessionLogout', {
- autoClose: false,
- });
- });
- });
-
- test('should show a warning toast before session expiration', async () => {
- jest.useFakeTimers();
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }: { children?: ReactNode }) => (
-
- {children}
-
- ),
- });
-
- act(() => {
- result.current.startSession();
- });
-
- act(() => {
- jest.advanceTimersByTime(15 * 60 * 1000);
- });
-
- await waitFor(() =>
- expect(toast.warning).toHaveBeenCalledWith('sessionWarning'),
- );
-
- jest.useRealTimers();
- });
-
- test('should handle error when revoking token fails', async () => {
- const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation();
-
- const errorMocks = [
- {
- request: {
- query: GET_COMMUNITY_SESSION_TIMEOUT_DATA,
- },
- result: {
- data: {
- getCommunityData: {
- timeout: 30,
- },
- },
- },
- delay: 1000,
- },
- {
- request: {
- query: REVOKE_REFRESH_TOKEN,
- },
- error: new Error('Failed to revoke refresh token'),
- },
- ];
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }: { children?: ReactNode }) => (
-
- {children}
-
- ),
- });
-
- act(() => {
- result.current.startSession();
- result.current.handleLogout();
- });
-
- await waitFor(() =>
- expect(consoleErrorMock).toHaveBeenCalledWith(
- 'Error revoking refresh token:',
- expect.any(Error),
- ),
- );
-
- consoleErrorMock.mockRestore();
- });
-
- test('should set session timeout based on fetched data', async () => {
- jest.spyOn(global, 'setTimeout');
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }: { children?: ReactNode }) => (
-
- {children}
-
- ),
- });
-
- act(() => {
- result.current.startSession();
- });
-
- expect(global.setTimeout).toHaveBeenCalled();
- });
-
- test('should call errorHandler on query error', async () => {
- const errorMocks = [
- {
- request: {
- query: GET_COMMUNITY_SESSION_TIMEOUT_DATA,
- },
- error: new Error('An error occurred'),
- },
- ];
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }: { children?: ReactNode }) => (
-
- {children}
-
- ),
- });
-
- act(() => {
- result.current.startSession();
- });
-
- await waitFor(() => expect(errorHandler).toHaveBeenCalled());
- });
- //dfghjkjhgfds
-
- test('should remove event listeners on endSession', async () => {
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }: { children?: ReactNode }) => (
-
- {children}
-
- ),
- });
-
- // Mock the removeEventListener functions for both window and document
- const removeEventListenerMock = jest.fn();
-
- // Temporarily replace the real methods with the mock
- const originalWindowRemoveEventListener = window.removeEventListener;
- const originalDocumentRemoveEventListener = document.removeEventListener;
-
- window.removeEventListener = removeEventListenerMock;
- document.removeEventListener = removeEventListenerMock;
-
- // await waitForNextUpdate();
-
- act(() => {
- result.current.startSession();
- });
-
- act(() => {
- result.current.endSession();
- });
-
- // Test that event listeners were removed
- expect(removeEventListenerMock).toHaveBeenCalledWith(
- 'mousemove',
- expect.any(Function),
- );
- expect(removeEventListenerMock).toHaveBeenCalledWith(
- 'keydown',
- expect.any(Function),
- );
- expect(removeEventListenerMock).toHaveBeenCalledWith(
- 'visibilitychange',
- expect.any(Function),
- );
-
- // Restore the original removeEventListener functions
- window.removeEventListener = originalWindowRemoveEventListener;
- document.removeEventListener = originalDocumentRemoveEventListener;
- });
-
- test('should call initialize timers when session is still active when the user returns to the tab', async () => {
- jest.useFakeTimers();
- jest.spyOn(global, 'setTimeout').mockImplementation(jest.fn());
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }) => (
-
- {children}
-
- ),
- });
-
- jest.advanceTimersByTime(1000);
-
- // Set initial visibility state to visible
- Object.defineProperty(document, 'visibilityState', {
- value: 'visible',
- writable: true,
- });
-
- // Start the session
- act(() => {
- result.current.startSession();
- jest.advanceTimersByTime(10 * 60 * 1000); // Fast-forward
- });
-
- // Simulate the user leaving the tab (set visibility to hidden)
- Object.defineProperty(document, 'visibilityState', {
- value: 'hidden',
- writable: true,
- });
-
- act(() => {
- document.dispatchEvent(new Event('visibilitychange'));
- });
-
- // Fast-forward time by more than the session timeout
- act(() => {
- jest.advanceTimersByTime(5 * 60 * 1000); // Fast-forward
- });
-
- // Simulate the user returning to the tab
- Object.defineProperty(document, 'visibilityState', {
- value: 'visible',
- writable: true,
- });
-
- act(() => {
- document.dispatchEvent(new Event('visibilitychange'));
- });
-
- jest.advanceTimersByTime(1000);
-
- expect(global.setTimeout).toHaveBeenCalled();
-
- // Restore real timers
- jest.useRealTimers();
- });
-
- test('should call handleLogout when session expires due to inactivity away from tab', async () => {
- jest.useFakeTimers(); // Use fake timers to control time
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }) => (
-
- {children}
-
- ),
- });
-
- jest.advanceTimersByTime(1000);
-
- // Set initial visibility state to visible
- Object.defineProperty(document, 'visibilityState', {
- value: 'visible',
- writable: true,
- });
-
- // Start the session
- act(() => {
- result.current.startSession();
- jest.advanceTimersByTime(10 * 60 * 1000); // Fast-forward
- });
-
- // Simulate the user leaving the tab (set visibility to hidden)
- Object.defineProperty(document, 'visibilityState', {
- value: 'hidden',
- writable: true,
- });
-
- act(() => {
- document.dispatchEvent(new Event('visibilitychange'));
- });
-
- // Fast-forward time by more than the session timeout
- act(() => {
- jest.advanceTimersByTime(32 * 60 * 1000); // Fast-forward by 32 minutes
- });
-
- // Simulate the user returning to the tab
- Object.defineProperty(document, 'visibilityState', {
- value: 'visible',
- writable: true,
- });
-
- act(() => {
- document.dispatchEvent(new Event('visibilitychange'));
- });
-
- jest.advanceTimersByTime(250);
-
- await waitFor(() => {
- expect(global.localStorage.clear).toHaveBeenCalled();
- expect(toast.warning).toHaveBeenCalledWith('sessionLogout', {
- autoClose: false,
- });
- });
-
- // Restore real timers
- jest.useRealTimers();
- });
-
- test('should handle logout and revoke token', async () => {
- jest.useFakeTimers();
-
- const { result } = renderHook(() => useSession(), {
- wrapper: ({ children }: { children?: ReactNode }) => (
-
- {children}
-
- ),
- });
-
- act(() => {
- result.current.startSession();
- result.current.handleLogout();
- });
-
- await waitFor(() => {
- expect(global.localStorage.clear).toHaveBeenCalled();
- expect(toast.warning).toHaveBeenCalledWith('sessionLogout', {
- autoClose: false,
- });
- });
-
- jest.useRealTimers();
- });
-});