From 4cc7229871f77afce0a21e5cd8741f3c92c2b721 Mon Sep 17 00:00:00 2001 From: Roxanne Young Date: Thu, 5 Mar 2026 08:58:11 -0500 Subject: [PATCH 1/2] fix(mui-autocomplete): expand scroll check area for asyncautocomplete --- packages/autocomplete/src/lib/AsyncAutocomplete.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/autocomplete/src/lib/AsyncAutocomplete.tsx b/packages/autocomplete/src/lib/AsyncAutocomplete.tsx index 328f69a33..a861ef9bc 100644 --- a/packages/autocomplete/src/lib/AsyncAutocomplete.tsx +++ b/packages/autocomplete/src/lib/AsyncAutocomplete.tsx @@ -112,7 +112,7 @@ export const AsyncAutocomplete = < const difference = listboxNode.scrollHeight - (listboxNode.scrollTop + listboxNode.clientHeight); // Only fetch if we are near the bottom, not already fetching, and there are more results - if (difference <= 5 && !isLoading && !isFetching && hasNextPage) { + if (difference <= 10 && !isLoading && !isFetching && hasNextPage) { fetchNextPage(); } } @@ -142,7 +142,7 @@ export const AsyncAutocomplete = < ...ListboxProps, ref: setListboxRef, onScroll: handleAddingOptions, - onPointerEnter: handleAddingOptions, + onPointerEnter: handleAddingOptions }} /> ); From afa3552d831e41baa351bec639ada3d8e2d52364 Mon Sep 17 00:00:00 2001 From: Roxanne Young Date: Thu, 5 Mar 2026 09:47:25 -0500 Subject: [PATCH 2/2] test(mui-autocomplete): add scroll and fallback loadoptions tests for asyncautocomplete --- .../src/lib/AsyncAutocomplete.test.tsx | 137 ++++++++++-------- 1 file changed, 80 insertions(+), 57 deletions(-) diff --git a/packages/autocomplete/src/lib/AsyncAutocomplete.test.tsx b/packages/autocomplete/src/lib/AsyncAutocomplete.test.tsx index 5d95559f1..31464a094 100644 --- a/packages/autocomplete/src/lib/AsyncAutocomplete.test.tsx +++ b/packages/autocomplete/src/lib/AsyncAutocomplete.test.tsx @@ -5,6 +5,7 @@ import { server } from '@availity/mock/src/lib/server'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AsyncAutocomplete } from './AsyncAutocomplete'; +import { mock } from 'node:test'; const api = new AvApi({ name: 'example' } as ApiConfig); @@ -182,63 +183,85 @@ describe('AsyncAutocomplete', () => { }); }); - // test('should call loadOptions when scroll to the bottom', async () => { - // const client = new QueryClient(); - - // render( - // - // - // - // ); - - // const input = screen.getByRole('combobox'); - // fireEvent.click(input); - // fireEvent.keyDown(input, { key: 'ArrowDown' }); - - // await waitFor(() => { - // expect(screen.getByText('Option 0')).toBeDefined(); - // expect(() => screen.getByText('Option 10')).toThrow(); - // }); - - // await act(async () => { - // const options = await screen.findByRole('listbox'); - // fireEvent.scroll(options, { target: { scrollTop: options.scrollHeight } }); - // }); - - // await waitFor(() => { - // expect(screen.getByText('Option 10')).toBeDefined(); - // expect(() => screen.getByText('Option 20')).toThrow(); - // }); - // }); - - // test('should call loadOptions when mouse enters the listbox if not enough options for scroll', async () => { - // const client = new QueryClient(); - - // render( - // - // - // - // ); - - // const input = screen.getByRole('combobox'); - // fireEvent.click(input); - // fireEvent.keyDown(input, { key: 'ArrowDown' }); - - // await waitFor(() => { - // expect(screen.getByText('Option 0')).toBeDefined(); - // expect(() => screen.getByText('Option 5')).toThrow(); - // }); - - // await act(async () => { - // const options = await screen.findByRole('listbox'); - // fireEvent.mouseEnter(options); - // }); - - // await waitFor(() => { - // expect(screen.getByText('Option 5')).toBeDefined(); - // expect(() => screen.getByText('Option 10')).toThrow(); - // }); - // }); + test('should call loadOptions when scroll to the bottom', async () => { + const mockLoadOptions = jest.fn(loadOptions); + const client = new QueryClient(); + + render( + + + + ); + + const input = screen.getByRole('combobox'); + fireEvent.click(input); + fireEvent.keyDown(input, { key: 'ArrowUp' }); + + const listbox = await screen.findByRole('listbox'); + Object.defineProperty(listbox, 'scrollHeight', { value: 500, writable: true }); + Object.defineProperty(listbox, 'clientHeight', { value: 100, writable: true }); + Object.defineProperty(listbox, 'scrollTop', { value: 0, writable: true }); + + // wait for no-scroll loading fallback to finish before testing scroll behavior + await waitFor(() => expect(() => screen.getByAltText('Loading')).toThrow()); + const initialCallCount = mockLoadOptions.mock.calls.length; + + fireEvent.scroll(listbox, { target: { scrollTop: 200 } }); + + // should not loadOptions since not scrolled to bottom + await waitFor(() => { + expect(mockLoadOptions).toHaveBeenCalledTimes(initialCallCount); + }); + + fireEvent.scroll(listbox, { target: { scrollTop: 400 } }); + + await waitFor(() => { + expect(mockLoadOptions).toHaveBeenCalledTimes(initialCallCount + 1); + }); + }); + + test('should call loadOptions if more options available and not enough options for scroll', async () => { + const mockLoadOptions = jest.fn(loadOptions); + const client = new QueryClient(); + + render( + + + + ); + + const input = screen.getByRole('combobox'); + fireEvent.click(input); + fireEvent.keyDown(input, { key: 'ArrowDown' }); + + await waitFor(() => { + expect(mockLoadOptions).toHaveBeenCalled(); + }); + + // should load more options until enough options for scroll or no more options to load + const listbox = await screen.findByRole('listbox'); + Object.defineProperty(listbox, 'scrollHeight', { value: 500, writable: true }); + Object.defineProperty(listbox, 'clientHeight', { value: 100, writable: true }); + Object.defineProperty(listbox, 'scrollTop', { value: 0, writable: true }); + + await waitFor(() => expect(() => screen.getByAltText('Loading')).toThrow()); + + // count number of calls before satisfying scrollable condition + const maxCallCount = mockLoadOptions.mock.calls.length; + + fireEvent.pointerEnter(listbox); + + // should not fallback to load more options as scrollable condition is satisfied + await waitFor(() => { + expect(mockLoadOptions).toHaveBeenCalledTimes(maxCallCount); + }); + }); + test('should search with input value', async () => { const mockLoadOptions = jest.fn(async () => ({ options: [{ label: 'Option 1' }], hasMore: false, offset: 50 }));