mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Combobox: Invalidate async calls using counter instead of value (#95049)
* Combobox async: Invalidate using timestamp instead of value * Simulate keyboard delay in test * Use a counter instead of timestamps * Fix feedback * Expand tests to include case discussed on PR --------- Co-authored-by: Joao Silva <joao.silva@grafana.com>
This commit is contained in:
parent
beaac3c885
commit
5ee5c7af7b
@ -179,6 +179,8 @@ describe('Combobox', () => {
|
||||
return new Promise<ComboboxOption[]>((resolve) => setTimeout(() => resolve([{ value: 'first' }]), 1000));
|
||||
} else if (searchTerm === 'ab') {
|
||||
return new Promise<ComboboxOption[]>((resolve) => setTimeout(() => resolve([{ value: 'second' }]), 200));
|
||||
} else if (searchTerm === 'abc') {
|
||||
return new Promise<ComboboxOption[]>((resolve) => setTimeout(() => resolve([{ value: 'third' }]), 500));
|
||||
}
|
||||
return Promise.resolve([]);
|
||||
});
|
||||
@ -186,26 +188,39 @@ describe('Combobox', () => {
|
||||
render(<Combobox options={asyncOptions} value={null} onChange={onChangeHandler} />);
|
||||
|
||||
const input = screen.getByRole('combobox');
|
||||
await user.click(input); // First request
|
||||
await user.click(input);
|
||||
await user.keyboard('abc');
|
||||
jest.advanceTimersByTime(200); // Resolve the second request, should be ignored
|
||||
|
||||
await user.keyboard('ab'); // Second request
|
||||
jest.advanceTimersByTime(210); // Resolve the second request
|
||||
|
||||
let item: HTMLElement | null = await screen.findByRole('option', { name: 'second' });
|
||||
let firstItem = screen.queryByRole('option', { name: 'first' });
|
||||
let secondItem = screen.queryByRole('option', { name: 'second' });
|
||||
let thirdItem = screen.queryByRole('option', { name: 'third' });
|
||||
|
||||
expect(item).toBeInTheDocument();
|
||||
expect(firstItem).not.toBeInTheDocument();
|
||||
expect(secondItem).not.toBeInTheDocument();
|
||||
expect(thirdItem).not.toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1100); // Resolve the first request
|
||||
});
|
||||
jest.advanceTimersByTime(500); // Resolve the third request, should be shown
|
||||
|
||||
item = screen.queryByRole('option', { name: 'first' });
|
||||
firstItem = screen.queryByRole('option', { name: 'second' });
|
||||
firstItem = screen.queryByRole('option', { name: 'first' });
|
||||
secondItem = screen.queryByRole('option', { name: 'second' });
|
||||
thirdItem = await screen.findByRole('option', { name: 'third' });
|
||||
|
||||
expect(item).not.toBeInTheDocument();
|
||||
expect(firstItem).toBeInTheDocument();
|
||||
expect(firstItem).not.toBeInTheDocument();
|
||||
expect(secondItem).not.toBeInTheDocument();
|
||||
expect(thirdItem).toBeInTheDocument();
|
||||
|
||||
jest.advanceTimersByTime(1000); // Resolve the first request, should be ignored
|
||||
|
||||
firstItem = screen.queryByRole('option', { name: 'first' });
|
||||
secondItem = screen.queryByRole('option', { name: 'second' });
|
||||
thirdItem = screen.queryByRole('option', { name: 'third' });
|
||||
|
||||
expect(firstItem).not.toBeInTheDocument();
|
||||
expect(secondItem).not.toBeInTheDocument();
|
||||
expect(thirdItem).toBeInTheDocument();
|
||||
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
it('should allow custom value while async is being run', async () => {
|
||||
|
@ -7,16 +7,17 @@ type AsyncFn<T, V> = (value: T) => Promise<V>;
|
||||
* Used to prevent a faster call being overwritten by an earlier slower call.
|
||||
*/
|
||||
export function useLatestAsyncCall<T, V>(fn: AsyncFn<T, V>): AsyncFn<T, V> {
|
||||
const latestValue = useRef<T>();
|
||||
const latestValueCount = useRef<number>(0);
|
||||
|
||||
const wrappedFn = useCallback(
|
||||
(value: T) => {
|
||||
latestValue.current = value;
|
||||
latestValueCount.current++;
|
||||
const requestCount = latestValueCount.current;
|
||||
|
||||
return new Promise<V>((resolve, reject) => {
|
||||
fn(value).then((result) => {
|
||||
// Only resolve if the value is still the latest
|
||||
if (latestValue.current === value) {
|
||||
if (requestCount === latestValueCount.current) {
|
||||
resolve(result);
|
||||
} else {
|
||||
reject(new StaleResultError());
|
||||
|
Loading…
Reference in New Issue
Block a user