-
Notifications
You must be signed in to change notification settings - Fork 95
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add: Add a useReload hook to make the Reload component obsolete
The new useReload hook is based in useTiming. It calls a timing function before every reload to calculate the timeout before the reload. This timing function gets a isVisible argument passed. Using the argument the timing function can decide to extend the timeout when the current browser window is not visible.
- Loading branch information
1 parent
4ef0714
commit 2bf49dc
Showing
2 changed files
with
235 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
/* SPDX-FileCopyrightText: 2024 Greenbone AG | ||
* | ||
* SPDX-License-Identifier: AGPL-3.0-or-later | ||
*/ | ||
|
||
/* eslint-disable react/prop-types */ | ||
|
||
import {describe, test, expect, testing} from '@gsa/testing'; | ||
|
||
import {act, fireEvent, render, screen} from 'web/utils/testing'; | ||
|
||
import useReload from '../useReload'; | ||
|
||
const TestComponent = ({reload, timeout}) => { | ||
const [startTimer, clearTimer, isRunning] = useReload(reload, timeout); | ||
return ( | ||
<> | ||
<button onClick={startTimer} data-testid="startTimer"></button> | ||
<button onClick={clearTimer} data-testid="clearTimer"></button> | ||
<span data-testid="isRunning">{'' + isRunning}</span> | ||
</> | ||
); | ||
}; | ||
|
||
const runTimers = async () => { | ||
await act(async () => { | ||
await testing.advanceTimersToNextTimerAsync(); | ||
}); | ||
}; | ||
|
||
describe('useTiming', () => { | ||
test('should start a timer to reload', async () => { | ||
testing.useFakeTimers(); | ||
|
||
const reload = testing.fn(); | ||
const timeout = testing.fn().mockImplementation(() => 900); | ||
|
||
render(<TestComponent reload={reload} timeout={timeout} />); | ||
|
||
const isRunning = screen.getByTestId('isRunning'); | ||
|
||
expect(isRunning).toHaveTextContent('false'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).not.toHaveBeenCalled(); | ||
|
||
fireEvent.click(screen.getByTestId('startTimer')); | ||
|
||
expect(isRunning).toHaveTextContent('true'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).toHaveBeenCalledWith({isVisible: true}); | ||
|
||
timeout.mockClear(); | ||
|
||
await runTimers(); | ||
|
||
expect(isRunning).toHaveTextContent('false'); | ||
expect(reload).toHaveBeenCalled(); | ||
expect(timeout).not.toHaveBeenCalled(); | ||
}); | ||
|
||
test('should reload forever', async () => { | ||
testing.useFakeTimers(); | ||
|
||
const reload = testing.fn().mockResolvedValue(); | ||
const timeout = testing.fn().mockImplementation(() => 900); | ||
|
||
render(<TestComponent reload={reload} timeout={timeout} />); | ||
|
||
const isRunning = screen.getByTestId('isRunning'); | ||
|
||
expect(isRunning).toHaveTextContent('false'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).not.toHaveBeenCalled(); | ||
|
||
fireEvent.click(screen.getByTestId('startTimer')); | ||
|
||
expect(isRunning).toHaveTextContent('true'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).toHaveBeenCalledWith({isVisible: true}); | ||
|
||
timeout.mockClear(); | ||
|
||
await runTimers(); | ||
|
||
expect(isRunning).toHaveTextContent('true'); | ||
expect(reload).toHaveBeenCalled(); | ||
expect(timeout).toHaveBeenCalledWith({isVisible: true}); | ||
|
||
timeout.mockClear(); | ||
reload.mockClear(); | ||
|
||
await runTimers(); | ||
|
||
expect(isRunning).toHaveTextContent('true'); | ||
expect(reload).toHaveBeenCalled(); | ||
expect(timeout).toHaveBeenCalledWith({isVisible: true}); | ||
}); | ||
|
||
test('should not reload if loading fails', async () => { | ||
testing.useFakeTimers(); | ||
|
||
const reload = testing.fn().mockRejectedValue(); | ||
const timeout = testing.fn().mockImplementation(() => 900); | ||
|
||
render(<TestComponent reload={reload} timeout={timeout} />); | ||
|
||
const isRunning = screen.getByTestId('isRunning'); | ||
|
||
expect(isRunning).toHaveTextContent('false'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).not.toHaveBeenCalled(); | ||
|
||
fireEvent.click(screen.getByTestId('startTimer')); | ||
|
||
expect(isRunning).toHaveTextContent('true'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).toHaveBeenCalledWith({isVisible: true}); | ||
|
||
timeout.mockClear(); | ||
|
||
await runTimers(); | ||
|
||
expect(isRunning).toHaveTextContent('false'); | ||
expect(reload).toHaveBeenCalled(); | ||
expect(timeout).not.toHaveBeenCalled(); | ||
}); | ||
|
||
test('should allow to cancel reload', async () => { | ||
testing.useFakeTimers(); | ||
|
||
const reload = testing.fn().mockResolvedValue(); | ||
const timeout = testing.fn().mockImplementation(() => 900); | ||
|
||
render(<TestComponent reload={reload} timeout={timeout} />); | ||
|
||
const isRunning = screen.getByTestId('isRunning'); | ||
|
||
expect(isRunning).toHaveTextContent('false'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).not.toHaveBeenCalled(); | ||
|
||
fireEvent.click(screen.getByTestId('startTimer')); | ||
|
||
expect(isRunning).toHaveTextContent('true'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).toHaveBeenCalledWith({isVisible: true}); | ||
|
||
timeout.mockClear(); | ||
|
||
await runTimers(); | ||
|
||
expect(isRunning).toHaveTextContent('true'); | ||
expect(reload).toHaveBeenCalled(); | ||
expect(timeout).toHaveBeenCalledWith({isVisible: true}); | ||
|
||
timeout.mockClear(); | ||
reload.mockClear(); | ||
|
||
fireEvent.click(screen.getByTestId('clearTimer')); | ||
|
||
await runTimers(); | ||
|
||
expect(isRunning).toHaveTextContent('false'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).not.toHaveBeenCalled(); | ||
}); | ||
|
||
test('should not start reload reload timer', async () => { | ||
testing.useFakeTimers(); | ||
|
||
const reload = testing.fn(); | ||
const timeout = testing.fn(); | ||
|
||
render(<TestComponent reload={reload} timeout={timeout} />); | ||
|
||
const isRunning = screen.getByTestId('isRunning'); | ||
|
||
expect(isRunning).toHaveTextContent('false'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).not.toHaveBeenCalled(); | ||
|
||
fireEvent.click(screen.getByTestId('startTimer')); | ||
|
||
expect(isRunning).toHaveTextContent('false'); | ||
expect(reload).not.toHaveBeenCalled(); | ||
expect(timeout).toHaveBeenCalledWith({isVisible: true}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* SPDX-FileCopyrightText: 2024 Greenbone AG | ||
* | ||
* SPDX-License-Identifier: AGPL-3.0-or-later | ||
*/ | ||
|
||
import {useCallback, useEffect} from 'react'; | ||
|
||
import useTiming from 'web/hooks/useTiming'; | ||
|
||
/** | ||
* A hook to reload data considering the visibility change of the browser tab. | ||
* | ||
* @param {Function} reloadFunc Function to call when the timer fires | ||
* @param {Function} timeoutFunc Function to get the timeout value from | ||
* @returns Array of startTimer function, clearTimer function and boolean isRunning | ||
*/ | ||
const useReload = (reloadFunc, timeoutFunc) => { | ||
const timeout = useCallback( | ||
() => timeoutFunc({isVisible: !document.hidden}), | ||
[timeoutFunc], | ||
); | ||
|
||
const [startTimer, clearTimer, isRunning] = useTiming(reloadFunc, timeout); | ||
|
||
const handleVisibilityChange = useCallback(() => { | ||
const isVisible = !document.hidden; | ||
|
||
if (isVisible) { | ||
// browser tab is visible again | ||
// restart timer to get a possible shorter interval as the remaining time | ||
|
||
clearTimer(); | ||
startTimer(); | ||
} | ||
}, [clearTimer, startTimer]); | ||
|
||
useEffect(() => { | ||
document.addEventListener('visibilitychange', handleVisibilityChange); | ||
return () => { | ||
document.removeEventListener('visibilitychange', handleVisibilityChange); | ||
}; | ||
}, [handleVisibilityChange]); | ||
|
||
return [startTimer, clearTimer, isRunning]; | ||
}; | ||
|
||
export default useReload; |