Throttle idle event listeners (#16555)

* throttle idle event listeners

* export timeout duration to use it in test directly
This commit is contained in:
Azri Kahar
2023-01-10 01:13:49 +08:00
committed by GitHub
parent 216930ee92
commit bc82c7bb8c
2 changed files with 105 additions and 6 deletions

95
app/src/idle.test.ts Normal file
View File

@@ -0,0 +1,95 @@
import { mount } from '@vue/test-utils';
import { afterEach, beforeEach, describe, expect, SpyInstance, test, vi } from 'vitest';
import { DefineComponent, defineComponent, h, onMounted, onUnmounted } from 'vue';
import { time as timeoutDuration } from './idle';
vi.mock('lodash', () => ({
throttle: vi.fn((fn, _wait) => fn),
}));
describe('idle', () => {
let testComponent: DefineComponent<any>;
let idleTrackerEmitSpy: SpyInstance;
beforeEach(async () => {
vi.useFakeTimers();
const { idleTracker, startIdleTracking, stopIdleTracking } = await import('./idle');
testComponent = defineComponent({
setup() {
onMounted(() => startIdleTracking());
onUnmounted(() => stopIdleTracking());
},
render: () => h('div'),
});
idleTrackerEmitSpy = vi.spyOn(idleTracker, 'emit');
});
afterEach(() => {
vi.useRealTimers();
// Ensure the internal visible & idle variables in the imported idle
// are reset before every test
vi.resetModules();
});
test('should emit "hide"/"show" when document visibility changes', () => {
mount(testComponent);
// mock document visibility state
Object.defineProperty(document, 'visibilityState', { value: 'hidden', configurable: true });
document.dispatchEvent(new Event('visibilitychange'));
expect(idleTrackerEmitSpy).toHaveBeenCalledWith('hide');
// mock document visibility state
Object.defineProperty(document, 'visibilityState', { value: 'visible', configurable: true });
document.dispatchEvent(new Event('visibilitychange'));
expect(idleTrackerEmitSpy).toHaveBeenCalledWith('show');
});
test('should not emit "idle" before the timeout has passed', () => {
mount(testComponent);
document.dispatchEvent(new PointerEvent('pointerdown'));
// advance less than the idle timeout duration
vi.advanceTimersByTime(1000);
expect(idleTrackerEmitSpy).not.toHaveBeenCalledWith('idle');
});
test('should emit "idle" after the timeout has passed', () => {
mount(testComponent);
document.dispatchEvent(new PointerEvent('pointerdown'));
// advance past the idle timeout duration (added 1000 just in case there's timing issues)
vi.advanceTimersByTime(timeoutDuration + 1000);
expect(idleTrackerEmitSpy).toHaveBeenCalledWith('idle');
});
test('should emit "active" after being idle', () => {
mount(testComponent);
document.dispatchEvent(new PointerEvent('pointerdown'));
// advance past the idle timeout duration (added 1000 just in case there's timing issues)
vi.advanceTimersByTime(timeoutDuration + 1000);
// stop the current idle state
document.dispatchEvent(new PointerEvent('pointerdown'));
// advance past the throttle duration (500)
vi.advanceTimersByTime(1000);
expect(idleTrackerEmitSpy).toHaveBeenCalledWith('active');
});
});

View File

@@ -1,20 +1,23 @@
import { throttle } from 'lodash';
import mitt from 'mitt';
const events = ['pointermove', 'pointerdown', 'keydown'];
const time = 5 * 60 * 1000; // 5 min in ms
export const time = 5 * 60 * 1000; // 5 min in ms
let timeout: NodeJS.Timeout;
let timeout: number | null;
let visible = true;
let idle = false;
export const idleTracker = mitt();
const throttledOnIdleEvents = throttle(onIdleEvents, 500);
export function startIdleTracking(): void {
document.addEventListener('visibilitychange', onVisibilityChange);
for (const event of events) {
document.addEventListener(event, onIdleEvents);
document.addEventListener(event, throttledOnIdleEvents);
}
resetTimeout();
@@ -24,7 +27,7 @@ export function stopIdleTracking(): void {
document.removeEventListener('visibilitychange', onVisibilityChange);
for (const event of events) {
document.removeEventListener(event, onIdleEvents);
document.removeEventListener(event, throttledOnIdleEvents);
}
}
@@ -51,10 +54,11 @@ function onVisibilityChange() {
function resetTimeout() {
if (timeout) {
clearTimeout(timeout);
window.clearTimeout(timeout);
timeout = null;
}
timeout = setTimeout(() => {
timeout = window.setTimeout(() => {
idle = true;
idleTracker.emit('idle');
}, time);