Skip to content
Open
70 changes: 37 additions & 33 deletions src/function/debounce.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,110 +3,116 @@ import { debounce } from './debounce';
// adjust the import path as necessary
import { delay } from '../promise';

const DEBOUNCE_MS = 50;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reduce code duplication, change debounceMs to a constant.


describe('debounce', () => {
it('should debounce function calls', async () => {
const func = vi.fn();
const debounceMs = 50;
const debouncedFunc = debounce(func, debounceMs);
const debouncedFunc = debounce(func, DEBOUNCE_MS);

debouncedFunc();
debouncedFunc();
debouncedFunc();

await delay(debounceMs * 2);
expect(func).not.toHaveBeenCalled();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was added to more clearly test the basic debounce behavior.


await delay(DEBOUNCE_MS * 2);

expect(func).toHaveBeenCalledTimes(1);
});

it('should delay the function call by the specified wait time', async () => {
const func = vi.fn();
const debounceMs = 50;
const debouncedFunc = debounce(func, debounceMs);
const debouncedFunc = debounce(func, DEBOUNCE_MS);

debouncedFunc();
await delay(debounceMs / 2);
await delay(DEBOUNCE_MS / 2);
expect(func).not.toHaveBeenCalled();

await delay(debounceMs / 2 + 1);
await delay(DEBOUNCE_MS / 2 + 1);
expect(func).toHaveBeenCalledTimes(1);
});

it('should reset the wait time if called again before wait time ends', async () => {
const func = vi.fn();
const debounceMs = 50;
const debouncedFunc = debounce(func, debounceMs);
const debouncedFunc = debounce(func, DEBOUNCE_MS);

debouncedFunc();
await delay(debounceMs / 2);
await delay(DEBOUNCE_MS / 2);
debouncedFunc();
await delay(debounceMs / 2);
await delay(DEBOUNCE_MS / 2);
debouncedFunc();
await delay(debounceMs / 2);
await delay(DEBOUNCE_MS / 2);
debouncedFunc();

expect(func).not.toHaveBeenCalled();

await delay(debounceMs + 1);
await delay(DEBOUNCE_MS + 1);
expect(func).toHaveBeenCalledTimes(1);
});

it('should cancel the debounced function call', async () => {
const func = vi.fn();
const debounceMs = 50;
const debouncedFunc = debounce(func, debounceMs);
const debouncedFunc = debounce(func, DEBOUNCE_MS);

debouncedFunc();
debouncedFunc.cancel();
await delay(debounceMs);
await delay(DEBOUNCE_MS);

expect(func).not.toHaveBeenCalled();
});

it('should immediately invoke the delayed function when flush is called', async () => {
const func = vi.fn();
const debouncedFunc = debounce(func, DEBOUNCE_MS);

debouncedFunc();
debouncedFunc.flush();

expect(func).toHaveBeenCalledTimes(1);
});
Comment on lines +65 to +73
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add test code for the debounced.flush function.


it('should work correctly if the debounced function is called after the wait time', async () => {
const func = vi.fn();
const debounceMs = 50;
const debouncedFunc = debounce(func, debounceMs);
const debouncedFunc = debounce(func, DEBOUNCE_MS);

debouncedFunc();
await delay(debounceMs + 1);
await delay(DEBOUNCE_MS + 1);
debouncedFunc();
await delay(debounceMs + 1);
await delay(DEBOUNCE_MS + 1);

expect(func).toHaveBeenCalledTimes(2);
});

it('should have no effect if we call cancel when the function is not executed', () => {
const func = vi.fn();
const debounceMs = 50;
const debouncedFunc = debounce(func, debounceMs);
const debouncedFunc = debounce(func, DEBOUNCE_MS);

expect(() => debouncedFunc.cancel()).not.toThrow();
});

it('should call the function with correct arguments', async () => {
const func = vi.fn();
const debounceMs = 50;
const debouncedFunc = debounce(func, debounceMs);
const debouncedFunc = debounce(func, DEBOUNCE_MS);

debouncedFunc('test', 123);

await delay(debounceMs * 2);
await delay(DEBOUNCE_MS * 2);

expect(func).toHaveBeenCalledTimes(1);
expect(func).toHaveBeenCalledWith('test', 123);
});

it('should cancel the debounced function call if aborted via AbortSignal', async () => {
const func = vi.fn();
const debounceMs = 50;
const controller = new AbortController();
const signal = controller.signal;
const debouncedFunc = debounce(func, debounceMs, { signal });
const debouncedFunc = debounce(func, DEBOUNCE_MS, { signal });

debouncedFunc();
controller.abort();

await delay(debounceMs);
await delay(DEBOUNCE_MS);

expect(func).not.toHaveBeenCalled();
});
Expand All @@ -119,24 +125,22 @@ describe('debounce', () => {

const func = vi.fn();

const debounceMs = 50;
const debouncedFunc = debounce(func, debounceMs, { signal });
const debouncedFunc = debounce(func, DEBOUNCE_MS, { signal });

debouncedFunc();

await delay(debounceMs);
await delay(DEBOUNCE_MS);

expect(func).not.toHaveBeenCalled();
});

it('should not add multiple abort event listeners', async () => {
const func = vi.fn();
const debounceMs = 100;
const controller = new AbortController();
const signal = controller.signal;
const addEventListenerSpy = vi.spyOn(signal, 'addEventListener');

const debouncedFunc = debounce(func, debounceMs, { signal });
const debouncedFunc = debounce(func, DEBOUNCE_MS, { signal });

debouncedFunc();
debouncedFunc();
Expand Down