| 1 | /** |
| 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | * |
| 4 | * This source code is licensed under the MIT license found in the |
| 5 | * LICENSE file in the root directory of this source tree. |
| 6 | */ |
| 7 | |
| 8 | import {gc} from 'shared/testUtils'; |
| 9 | import {Timer} from '../timer'; |
| 10 | |
| 11 | describe('Timer', () => { |
| 12 | beforeEach(() => { |
| 13 | jest.useFakeTimers(); |
| 14 | }); |
| 15 | afterEach(() => { |
| 16 | jest.useRealTimers(); |
| 17 | }); |
| 18 | |
| 19 | it('can enable and disable', () => { |
| 20 | const callback = jest.fn(); |
| 21 | const timer = new Timer(callback, 100); |
| 22 | |
| 23 | timer.enabled = true; |
| 24 | jest.advanceTimersByTime(250); |
| 25 | expect(callback).toHaveBeenCalledTimes(2); |
| 26 | |
| 27 | timer.enabled = false; |
| 28 | jest.advanceTimersByTime(500); |
| 29 | expect(callback).toHaveBeenCalledTimes(2); |
| 30 | |
| 31 | timer.enabled = true; |
| 32 | jest.advanceTimersByTime(200); |
| 33 | expect(callback).toHaveBeenCalledTimes(4); |
| 34 | }); |
| 35 | |
| 36 | it('error once cancels the timer', () => { |
| 37 | const callback = jest.fn(() => { |
| 38 | throw new Error('x'); |
| 39 | }); |
| 40 | |
| 41 | // Initially enabled. |
| 42 | const timer = new Timer(callback, 100, true); |
| 43 | expect(timer.enabled).toBe(true); |
| 44 | |
| 45 | // Try to call 3 times, but the first time it will throw. |
| 46 | try { |
| 47 | jest.advanceTimersByTime(350); |
| 48 | } catch (_e) {} |
| 49 | |
| 50 | // After throw the timer is disabled. |
| 51 | expect(timer.enabled).toBe(false); |
| 52 | expect(callback).toHaveBeenCalledTimes(1); |
| 53 | }); |
| 54 | |
| 55 | it('returning false stops the timer', () => { |
| 56 | let count = 0; |
| 57 | const callback = jest.fn(() => { |
| 58 | count += 1; |
| 59 | return count < 3; |
| 60 | }); |
| 61 | const timer = new Timer(callback, 100, true); |
| 62 | expect(timer.enabled).toBe(true); |
| 63 | jest.advanceTimersByTime(250); |
| 64 | expect(timer.enabled).toBe(true); |
| 65 | jest.advanceTimersByTime(500); |
| 66 | expect(timer.enabled).toBe(false); |
| 67 | expect(callback).toHaveBeenCalledTimes(3); |
| 68 | }); |
| 69 | |
| 70 | it('dispose cancels the timer forever', () => { |
| 71 | const callback = jest.fn(); |
| 72 | const timer = new Timer(callback, 100); |
| 73 | |
| 74 | timer.enabled = true; |
| 75 | jest.advanceTimersByTime(50); |
| 76 | |
| 77 | timer.dispose(); |
| 78 | |
| 79 | // Cannot be re-enabled. |
| 80 | timer.enabled = true; |
| 81 | jest.advanceTimersByTime(500); |
| 82 | expect(timer.enabled).toBe(false); |
| 83 | expect(callback).toHaveBeenCalledTimes(0); |
| 84 | }); |
| 85 | |
| 86 | it('GC cancels the timer forever', async () => { |
| 87 | const callback = jest.fn(); |
| 88 | let timer: Timer | null = new Timer(callback, 100, true); |
| 89 | expect(timer.enabled).toBe(true); |
| 90 | |
| 91 | // GC the timer. |
| 92 | // eslint-disable-next-line @typescript-eslint/no-unused-vars |
| 93 | timer = null; |
| 94 | await gc(); |
| 95 | |
| 96 | // Callback should not be called after the time is gone. |
| 97 | jest.advanceTimersByTime(500); |
| 98 | expect(callback).toHaveBeenCalledTimes(0); |
| 99 | }); |
| 100 | }); |
| 101 | |