Skip to content

Instantly share code, notes, and snippets.

@kapral18
Last active September 15, 2025 11:36
Show Gist options
  • Save kapral18/1032800f0963dbc288dbf5882a6f5e22 to your computer and use it in GitHub Desktop.
Save kapral18/1032800f0963dbc288dbf5882a6f5e22 to your computer and use it in GitHub Desktop.
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { waitFor, waitForElementToBeRemoved, screen, render, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
describe('Basic scenario', () => {
beforeEach(() => {
simpleTimer.callCount = 0;
});
async function simpleTimer(callback: any) {
simpleTimer.callCount += 1;
if (simpleTimer.callCount > 4) {
return;
}
// await is the microtask boundary before scheduling the next timer
// jest fake timer calls do not advance the microtask queue
// only macrotasks (setTimeout, setInterval) are advanced
// even if you use runAllTimers or advanceTimersByTime
// so the await here means the next setTimeout is never scheduled
await callback();
setTimeout(() => {
simpleTimer(callback);
}, 1000);
}
simpleTimer.callCount = 0;
const callback = jest.fn();
it('fakeTimers work well with promise microtasks queues', async () => {
jest.useFakeTimers();
await simpleTimer(callback);
await jest.advanceTimersByTimeAsync(4000);
expect(callback).toHaveBeenCalledTimes(4);
});
it('real timers with waitFor work fine with microtasks', async () => {
await simpleTimer(callback);
await waitFor(() => {
expect(callback).toHaveBeenCalledTimes(4);
});
});
});
describe('Component scenarios', () => {
const api = {
save: jest.fn().mockResolvedValue(null),
};
function AutoSaveForm() {
const [status, setStatus] = React.useState<'idle' | 'saving' | 'saved'>('idle');
async function onChange(value: string) {
setStatus('saving');
await api.save(value); // microtask boundary before scheduling the next timer
setStatus('saved');
setTimeout(() => setStatus('idle'), 1000);
}
return (
<>
<label htmlFor="name">Name</label>
<input id="name" onChange={(e) => onChange(e.target.value)} />
{status !== 'idle' && <output>{status}</output>}
</>
);
}
describe('Success with fake timers', () => {
it('shows and hides saved toasts', async () => {
render(<AutoSaveForm />);
await user.type(screen.getByLabelText('Name', { selector: 'input' }), 'abc');
await act(async () => {
await jest.advanceTimersByTimeAsync(999);
});
expect(screen.getByRole('status')).toHaveTextContent(/saved/i);
await act(async () => {
await jest.runAllTimersAsync();
});
expect(screen.queryByRole('status')).not.toBeInTheDocument();
});
it('shows and hides saved toasts (fake timers + waitForElementToBeRemoved)', async () => {
render(<AutoSaveForm />);
await user.type(screen.getByLabelText('Name', { selector: 'input' }), 'abc');
await act(async () => {
await jest.advanceTimersByTimeAsync(999);
});
expect(screen.getByRole('status')).toHaveTextContent(/saved/i);
await waitForElementToBeRemoved(() => screen.queryByRole('status'));
});
it('shows and hides saved toats (fake timers + waitFor)', async () => {
render(<AutoSaveForm />);
await user.type(screen.getByLabelText('Name', { selector: 'input' }), 'abc');
await act(async () => {
await jest.runAllTimersAsync();
});
await waitFor(() => expect(screen.queryByRole('status')).not.toBeInTheDocument());
});
});
describe('Success with real timers', () => {
it('shows and hides saved toast after async save (real timers)', async () => {
render(<AutoSaveForm />);
await user.type(screen.getByLabelText('Name', { selector: 'input' }), 'abc');
expect(await screen.findByText('saved', { selector: 'output' })).toBeInTheDocument();
await waitForElementToBeRemoved(() => screen.queryByRole('status'));
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment