Last active
June 10, 2025 14:22
-
-
Save shaggun/0b3f2aab4282b781d0a7acef28e19bfc to your computer and use it in GitHub Desktop.
Expanded onHighlight tests with edge cases
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import * as React from 'react'; | |
| import { expect } from 'chai'; | |
| import { createClientRender, fireEvent, screen, act } from 'test/utils'; | |
| import { spy } from 'sinon'; | |
| import Autocomplete from './Autocomplete'; | |
| import TextField from '../TextField'; | |
| describe('<Autocomplete /> - Bug fix for onHighlightChange with input changes', () => { | |
| const render = createClientRender(); | |
| it('should call onHighlightChange with correct option when input changes result in different filtered options', () => { | |
| const handleHighlightChange = spy(); | |
| const options = ['option 1 1', 'option 2 12', 'option 3 123']; | |
| render( | |
| <Autocomplete | |
| options={options} | |
| autoHighlight | |
| onHighlightChange={handleHighlightChange} | |
| renderInput={(params) => <TextField {...params} autoFocus />} | |
| />, | |
| ); | |
| const textbox = screen.getByRole('textbox'); | |
| // Clear any initial calls | |
| handleHighlightChange.resetHistory(); | |
| // Type "1" - should highlight "option 1 1" (first match) | |
| act(() => { | |
| fireEvent.change(textbox, { target: { value: '1' } }); | |
| }); | |
| // Check that the first option matching "1" is highlighted | |
| expect(handleHighlightChange.callCount).to.be.greaterThan(0); | |
| const firstCall = handleHighlightChange.lastCall; | |
| expect(firstCall.args[1]).to.equal('option 1 1'); | |
| expect(firstCall.args[2]).to.equal('auto'); | |
| // Reset spy to track next call clearly | |
| handleHighlightChange.resetHistory(); | |
| // Type "2" (so input becomes "12") - should highlight "option 2 12" (first match for "12") | |
| act(() => { | |
| fireEvent.change(textbox, { target: { value: '12' } }); | |
| }); | |
| // This should now correctly highlight "option 2 12" | |
| expect(handleHighlightChange.callCount).to.be.greaterThan(0); | |
| const secondCall = handleHighlightChange.lastCall; | |
| expect(secondCall.args[1]).to.equal('option 2 12'); | |
| expect(secondCall.args[2]).to.equal('auto'); | |
| }); | |
| it('should handle rapid typing correctly', () => { | |
| const handleHighlightChange = spy(); | |
| const options = ['apple', 'application', 'apply', 'banana', 'band', 'bandana']; | |
| render( | |
| <Autocomplete | |
| options={options} | |
| autoHighlight | |
| onHighlightChange={handleHighlightChange} | |
| renderInput={(params) => <TextField {...params} autoFocus />} | |
| />, | |
| ); | |
| const textbox = screen.getByRole('textbox'); | |
| handleHighlightChange.resetHistory(); | |
| // Type "a" - should highlight "apple" | |
| act(() => { | |
| fireEvent.change(textbox, { target: { value: 'a' } }); | |
| }); | |
| // Type "p" (input becomes "ap") - should highlight "apple" | |
| act(() => { | |
| fireEvent.change(textbox, { target: { value: 'ap' } }); | |
| }); | |
| // Type "p" (input becomes "app") - should highlight "apple" | |
| act(() => { | |
| fireEvent.change(textbox, { target: { value: 'app' } }); | |
| }); | |
| // Verify the last call has the correct option | |
| const lastCall = handleHighlightChange.lastCall; | |
| expect(lastCall.args[1]).to.equal('apple'); | |
| }); | |
| it('should update highlight when first option changes without input change', async () => { | |
| const handleHighlightChange = spy(); | |
| const TestComponent = () => { | |
| const [options, setOptions] = React.useState(['apple', 'apricot', 'application']); | |
| React.useEffect(() => { | |
| // After a short delay, reorder options so 'application' becomes first | |
| const timer = setTimeout(() => { | |
| act(() => { | |
| setOptions(['application', 'apricot', 'apple']); | |
| }); | |
| }, 50); | |
| return () => clearTimeout(timer); | |
| }, []); | |
| return ( | |
| <Autocomplete | |
| options={options} | |
| autoHighlight | |
| onHighlightChange={handleHighlightChange} | |
| renderInput={(params) => <TextField {...params} autoFocus />} | |
| /> | |
| ); | |
| }; | |
| render(<TestComponent />); | |
| const textbox = screen.getByRole('textbox'); | |
| // Type "ap" - initially should highlight "apple" (first match) | |
| act(() => { | |
| fireEvent.change(textbox, { target: { value: 'ap' } }); | |
| }); | |
| handleHighlightChange.resetHistory(); | |
| // Wait for options to change - this creates the edge case where: | |
| // - inputValue stays "ap" (no change) | |
| // - filteredOptions.length stays 3 (no change) | |
| // - but first option changes from "apple" to "application" | |
| await act(async () => { | |
| await new Promise((resolve) => { | |
| setTimeout(() => { | |
| // This should highlight "application" as the new first option | |
| expect(handleHighlightChange.callCount).to.be.greaterThan(0); | |
| const lastCall = handleHighlightChange.lastCall; | |
| expect(lastCall.args[1]).to.equal('application'); | |
| expect(lastCall.args[2]).to.equal('auto'); | |
| resolve(); | |
| }, 100); | |
| }); | |
| }); | |
| }); | |
| it('should update highlight when options array changes with same input and same length', async () => { | |
| const handleHighlightChange = spy(); | |
| const TestComponent = () => { | |
| const [options, setOptions] = React.useState(['banana', 'band', 'bandana']); | |
| React.useEffect(() => { | |
| // Change options while keeping same length and same filtering pattern | |
| const timer = setTimeout(() => { | |
| act(() => { | |
| setOptions(['band', 'banana', 'bandana']); // 'band' becomes first instead of 'banana' | |
| }); | |
| }, 50); | |
| return () => clearTimeout(timer); | |
| }, []); | |
| return ( | |
| <Autocomplete | |
| options={options} | |
| autoHighlight | |
| onHighlightChange={handleHighlightChange} | |
| renderInput={(params) => <TextField {...params} autoFocus />} | |
| /> | |
| ); | |
| }; | |
| render(<TestComponent />); | |
| const textbox = screen.getByRole('textbox'); | |
| // Type "ban" - initially should highlight "banana" | |
| act(() => { | |
| fireEvent.change(textbox, { target: { value: 'ban' } }); | |
| }); | |
| handleHighlightChange.resetHistory(); | |
| // Wait for options to change | |
| await act(async () => { | |
| await new Promise((resolve) => { | |
| setTimeout(() => { | |
| // Should now highlight "band" as new first option | |
| expect(handleHighlightChange.callCount).to.be.greaterThan(0); | |
| const lastCall = handleHighlightChange.lastCall; | |
| expect(lastCall.args[1]).to.equal('band'); | |
| expect(lastCall.args[2]).to.equal('auto'); | |
| resolve(); | |
| }, 100); | |
| }); | |
| }); | |
| }); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment