Skip to content

Commit 50b262e

Browse files
authored
Merge pull request #1452 from rjmunro/rjmunro/fix-clock-accuracy
2 parents 8bba9ef + 13242ed commit 50b262e

File tree

3 files changed

+72
-4
lines changed

3 files changed

+72
-4
lines changed

packages/webui/jest.config.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77
globals: {},
88
moduleFileExtensions: ['js', 'ts', 'tsx'],
99
moduleNameMapper: {
10+
'sha.js': 'sha.js',
1011
'meteor/(.*)': '<rootDir>/src/meteor/$1',
1112
'(.+)\\.js$': '$1',
1213
},
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useCurrentTime } from '../lib' // Adjust the import path as needed
2+
import { act, renderHook } from '@testing-library/react'
3+
4+
describe('useCurrentTime Hook', () => {
5+
it('should return the current time in milliseconds initially', () => {
6+
const initialTime = Date.now()
7+
const { result } = renderHook(() => useCurrentTime())
8+
expect(result.current).toBeGreaterThanOrEqual(initialTime)
9+
expect(result.current).toBeLessThanOrEqual(initialTime + 50) // Allow for a small margin
10+
})
11+
12+
it('should update the time after the default refresh period (1000ms)', () => {
13+
jest.useFakeTimers() // Enable Jest's fake timers
14+
const { result } = renderHook(() => useCurrentTime())
15+
const initialTime = result.current
16+
17+
act(() => {
18+
jest.advanceTimersByTime(1000) // Advance the timers by the default refresh period
19+
})
20+
21+
const nextSecond = Math.ceil((initialTime + 1) / 1000) * 1000 // Round to the next second
22+
expect(result.current).toBeGreaterThanOrEqual(nextSecond)
23+
expect(result.current).toBeLessThanOrEqual(nextSecond + 50) // Allow for a small margin
24+
})
25+
26+
it('should update the time after a custom refresh period', () => {
27+
jest.useFakeTimers() // Enable Jest's fake timers
28+
const refreshPeriod = 500
29+
const { result } = renderHook(() => useCurrentTime(refreshPeriod))
30+
const initialTime = result.current
31+
32+
act(() => {
33+
jest.advanceTimersByTime(refreshPeriod)
34+
})
35+
36+
const nextInterval = Math.ceil((initialTime + 1) / refreshPeriod) * refreshPeriod // Round to the next refreshPeriod
37+
expect(result.current).toBeGreaterThanOrEqual(nextInterval - 50)
38+
expect(result.current).toBeLessThanOrEqual(nextInterval + 50) // Allow for a small margin
39+
})
40+
41+
it('should clear the timeout on unmount', () => {
42+
const { unmount } = renderHook(() => useCurrentTime())
43+
const mockClearTimeout = jest.spyOn(global, 'clearTimeout')
44+
45+
unmount()
46+
47+
expect(mockClearTimeout).toHaveBeenCalledTimes(1)
48+
// You could also assert that the timeout ID stored in the ref was passed to clearTimeout
49+
})
50+
})

packages/webui/src/client/lib/lib.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,16 +178,33 @@ export function useDebounce<K>(value: K, delay: number, shouldUpdate?: (oldVal:
178178
return debouncedValue
179179
}
180180

181+
/**
182+
* This hook returns the current time and updates it at the specified refresh period.
183+
* It uses a timeout to schedule the next update based on the refresh period.
184+
* The time is calculated as the current time in milliseconds since the Unix epoch.
185+
* The hook also cleans up the timeout when the component using it unmounts.
186+
*
187+
* @param refreshPeriod - The period in milliseconds at which the current time should be refreshed.
188+
* @returns The current time in milliseconds since the Unix epoch.
189+
*/
181190
export function useCurrentTime(refreshPeriod = 1000): number {
182191
const [time, setTime] = useState(getCurrentTime())
192+
const timeoutId = useRef<ReturnType<typeof setTimeout> | null>(null)
183193

184194
useEffect(() => {
185-
const interval = setInterval(() => {
186-
setTime(getCurrentTime())
187-
}, refreshPeriod)
195+
function updateTime() {
196+
const current = getCurrentTime()
197+
setTime(current)
198+
const delay = refreshPeriod - (current % refreshPeriod)
199+
timeoutId.current = setTimeout(updateTime, delay)
200+
}
201+
202+
updateTime() // Initial call to start the timer
188203

189204
return () => {
190-
clearInterval(interval)
205+
if (timeoutId.current) {
206+
clearTimeout(timeoutId.current) // Cleanup on unmount
207+
}
191208
}
192209
}, [refreshPeriod])
193210

0 commit comments

Comments
 (0)