chore: `useAutoupdate` (#35466)
parent
e3ea1d8923
commit
20fef4d2a7
@ -0,0 +1,30 @@ |
||||
import { css } from '@rocket.chat/css-in-js'; |
||||
import { Box, Button } from '@rocket.chat/fuselage'; |
||||
import { useTranslation } from 'react-i18next'; |
||||
|
||||
import { useIdleDetection } from '../hooks/useIdleDetection'; |
||||
|
||||
export const AutoupdateToastMessage = () => { |
||||
const { t } = useTranslation(); |
||||
useIdleDetection( |
||||
() => { |
||||
window.location.reload(); |
||||
}, |
||||
{ awayOnWindowBlur: true }, |
||||
); |
||||
|
||||
return ( |
||||
<Box |
||||
display='flex' |
||||
alignItems='center' |
||||
className={css` |
||||
gap: 8px; |
||||
`}
|
||||
> |
||||
{t('An_update_is_available')} |
||||
<Button primary small onClick={() => window.location.reload()}> |
||||
{t('Reload_to_update')} |
||||
</Button> |
||||
</Box> |
||||
); |
||||
}; |
||||
@ -0,0 +1,54 @@ |
||||
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; |
||||
import { renderHook } from '@testing-library/react'; |
||||
|
||||
import { useAutoupdate } from './useAutoupdate'; |
||||
|
||||
jest.mock('@rocket.chat/ui-contexts', () => ({ |
||||
useToastMessageDispatch: jest.fn(() => jest.fn()), |
||||
})); |
||||
|
||||
jest.mock('react-i18next', () => ({ |
||||
useTranslation: () => ({ |
||||
t: (key: string) => key, |
||||
}), |
||||
})); |
||||
|
||||
describe('useAutoupdate', () => { |
||||
it('should add event listener to document on mount', () => { |
||||
const addEventListenerSpy = jest.spyOn(document, 'addEventListener'); |
||||
renderHook(() => useAutoupdate()); |
||||
expect(addEventListenerSpy).toHaveBeenCalled(); |
||||
expect(addEventListenerSpy).toHaveBeenCalledWith('client_changed', expect.any(Function)); |
||||
}); |
||||
|
||||
it('should remove event listener on unmount', () => { |
||||
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener'); |
||||
const { unmount } = renderHook(() => useAutoupdate()); |
||||
unmount(); |
||||
expect(removeEventListenerSpy).toHaveBeenCalled(); |
||||
expect(removeEventListenerSpy).toHaveBeenCalledWith('client_changed', expect.any(Function)); |
||||
}); |
||||
|
||||
it('should call toast function when client_changed event is fired', () => { |
||||
const toastMock = jest.fn(); |
||||
(useToastMessageDispatch as jest.Mock).mockImplementation(() => toastMock); |
||||
renderHook(() => useAutoupdate()); |
||||
|
||||
const event = new Event('client_changed'); |
||||
document.dispatchEvent(event); |
||||
|
||||
expect(toastMock).toHaveBeenCalledTimes(1); |
||||
expect(toastMock).toHaveBeenCalledWith({ |
||||
type: 'info', |
||||
message: expect.anything(), |
||||
options: { isPersistent: true }, |
||||
}); |
||||
}); |
||||
|
||||
it('should not call toast function when client_changed event is not fired', () => { |
||||
const toastMock = jest.fn(); |
||||
(useToastMessageDispatch as jest.Mock).mockImplementation(() => toastMock); |
||||
renderHook(() => useAutoupdate()); |
||||
expect(toastMock).not.toHaveBeenCalled(); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,32 @@ |
||||
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; |
||||
import { useEffect } from 'react'; |
||||
import { useTranslation } from 'react-i18next'; |
||||
|
||||
import { AutoupdateToastMessage } from '../components/AutoupdateToastMessage'; |
||||
|
||||
export const useAutoupdate = () => { |
||||
const toast = useToastMessageDispatch(); |
||||
const { t } = useTranslation(); |
||||
const isDevMode = process.env.NODE_ENV === 'development'; |
||||
|
||||
useEffect(() => { |
||||
const fn = () => { |
||||
// To test this feature locally, comment the if statement below
|
||||
if (isDevMode) { |
||||
window.location.reload(); |
||||
return; |
||||
} |
||||
toast({ |
||||
type: 'info', |
||||
options: { isPersistent: true }, |
||||
message: <AutoupdateToastMessage />, |
||||
}); |
||||
}; |
||||
|
||||
document.addEventListener('client_changed', fn); |
||||
|
||||
return () => { |
||||
document.removeEventListener('client_changed', fn); |
||||
}; |
||||
}, [isDevMode, t, toast]); |
||||
}; |
||||
@ -0,0 +1,56 @@ |
||||
import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; |
||||
import { useEffect } from 'react'; |
||||
|
||||
const events = ['mousemove', 'mousedown', 'touchend', 'touchstart', 'keypress']; |
||||
|
||||
/** |
||||
* useIdleDetection is a custom hook that triggers a callback function when the user is detected to be idle. |
||||
* The idle state is determined based on the absence of certain user interactions for a specified time period. |
||||
* |
||||
* @param callback - The callback function to be called when the user is detected to be idle. |
||||
* @param options - An optional configuration object. |
||||
* @param options.time - The time in milliseconds to consider the user idle. Defaults to 600000 ms (10 minutes). |
||||
* @param options.awayOnWindowBlur - A boolean flag to trigger the callback when the window loses focus. Defaults to false. |
||||
* |
||||
*/ |
||||
|
||||
export const useIdleDetection = (callback: () => void, { time = 600000, awayOnWindowBlur = false } = {}) => { |
||||
const stableCallback = useEffectEvent(callback); |
||||
|
||||
useEffect(() => { |
||||
let interval: ReturnType<typeof setTimeout>; |
||||
const handleIdle = () => { |
||||
clearTimeout(interval); |
||||
interval = setTimeout(() => { |
||||
document.dispatchEvent(new Event('idle')); |
||||
}, time); |
||||
}; |
||||
|
||||
handleIdle(); |
||||
|
||||
events.forEach((key) => document.addEventListener(key, handleIdle)); |
||||
return () => { |
||||
clearTimeout(interval); |
||||
events.forEach((key) => document.removeEventListener(key, handleIdle)); |
||||
}; |
||||
}, [stableCallback, time]); |
||||
|
||||
useEffect(() => { |
||||
if (!awayOnWindowBlur) { |
||||
return; |
||||
} |
||||
|
||||
window.addEventListener('blur', stableCallback); |
||||
return () => { |
||||
window.removeEventListener('blur', stableCallback); |
||||
}; |
||||
}, [awayOnWindowBlur, stableCallback]); |
||||
|
||||
useEffect(() => { |
||||
document.addEventListener('idle', stableCallback); |
||||
|
||||
return () => { |
||||
document.removeEventListener('idle', stableCallback); |
||||
}; |
||||
}, [stableCallback]); |
||||
}; |
||||
Loading…
Reference in new issue